★★ ★★ ★ tags: “iOS” “OC” “Objective-C” author: MrLiuQ

The table of contents is as follows: IOS write high-quality Objective-C code (1) iOS write high-quality Objective-C code (2) iOS write high-quality Objective-C code (3) iOS write high-quality Objective-C code (4) iOS Write high-quality Objective-C code for iOS (6) Write high-quality Objective-C code for iOS (7)


This article will examine how to improve OC code quality from an object-oriented perspective.

1. Understand the concept of “attributes”

Properties (@Property) are a feature of OC. @property: The compiler automatically generates instance variables and getter and setter methods. Getter and setter methods are called accessors and accessors.

@property (nonatomic, strong) UIView *qiShareView;
Copy the code

Is equivalent to:

@synthesize qiShareView = _qiShareView;
- (UIView *)qiShareView;
- (void)setQiShareView:(UIView *)qiShareView;
Copy the code

If you do not want to automatically generate access methods and instance variables, use the @dynamic keyword

@dynamic qiShareView;
Copy the code

There are four categories of attributes:

  1. Atomicity: The default isatomic
    • nonatomic: non-atomic, read/write without synchronization lock
    • atomic: atomicity, add synchronization lock when read and write
  2. Read and write permission: Default valuereadwrite
    • readwrite:getterandsettermethods
    • readonly: only havegettermethods
  3. Memory management:
    • assign: Simple assignment to scalar type (NSInteger,CGFloat, etc.).
    • strong: Strong ownership relationship, setting methodKeep the new value and release the old value.
    • weak: Weak ownership: Setting methodNew values are not retained and old values are not released. When the object to which the pointer points is destroyed, the pointer is setnil.
    • copy: Copy ownership relationship. Setting method Copy the property without retaining the new value.
    • unsafe_unretained: Non-owning relation, target object is released, pointer is not setnil, this point andassignThe same. The difference inweak
  4. The method name:
    • getter=<name>: Specifies the name of the get method
    • setter=<name>: Specifies the name of the set method.
@property (nonatomic, getter=isOn) BOOL on;
Copy the code

In iOS development, 99.99.. % will be declared nonatomic. One is that atomic can seriously affect performance, and the other is that atomic can only ensure that the process of read/write operations is reliable, not thread-safe. For the second point, please refer to my blog: Why is iOS still not thread-safe when the attribute is atomic?

Try to access instance variables directly from within the object

  1. Instance variable (_ attribute name) to access the object scenario:
    • ininitanddeallocMethod, you should always read and write data by accessing instance variables
    • There is no overridegetterandsetterMethod, also not usedKVOListening to the
    • Benefits: Not OC method distribution mechanism, direct access to memory read and write, fast, high efficiency.

For Example:

- (instancetype)initWithDic:(NSDictionary *)dic {
    
    self = [super init];
    
    if (self) {
        
        _qi = dic[@"qi"];
        _share = dic[@"share"];
    }
    
    return self;
}
Copy the code
  1. Scenarios where objects are accessed using accessor methods:
    • Rewrite thegetter/setterMethods (e.g., lazy loading)
    • Using theKVOListen for value changes

For Example:

- (UIView *)qiShareView {
  
    if(! _qiShareView) { _qiShareView = [UIView new]; }return _qiShareView;
}
Copy the code

Iii. Understanding “Object equality”

What is the output?

NSString *aString = @"iPhone 8";
NSString *bString = [NSString stringWithFormat:@"iPhone %i"8]; NSLog(@"%d", [aString isEqual:bString]);
NSLog(@"%d", [aString isEqualToString:bString]);
NSLog(@"%d", aString == bString);
Copy the code

The answer is 110 == only compares the address of the object to which the pointer points, not the value of the object to which the pointer points so that the last one is 0

4. Hide implementation details in the family pattern

Why is if always false in this example?

id maybeAnArray = @[];
if ([maybeAnArray class] == [NSArray class]) {
     //Code will never be executed
}
Copy the code

Because the return of [maybeAnArray Class] will never be an NSArray, NSArray is a family of classes, and the value returned will always be an entity subclass of NSArray. Most collection classes are abstract base classes in a family of classes, so the if above should be changed to have any chance of being executed

id maybeAnArray = @[];
if ([maybeAnArray isKindOfClass [NSArray class]) {
      // Code probably be executed
}
Copy the code

This means that the maybeAnArray object is a member of the NSArray family of classes. The advantage of using a family of classes is that you can hide implementation details behind a simple set of public interfaces

5. Use associated objects to store custom data in existing classes

Let’s start with the Runtime class library

#import <objc/runtime.h>
Copy the code
Objc_AssociationPolicy (Object association policy type) :
Objc_AssociationPolicy (AssociationPolicy type) Equivalent @property
OBJC_ASSOCIATION_ASSIGN Equivalent to the assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC Equivalent to nonatomic retain
OBJC_ASSOCIATION_COPY_NONATOMIC Equivalent to nonatomic copy
OBJC_ASSOCIATION_RETAIN Equivalent to retain
OBJC_ASSOCIATION_COPY Equivalent to the copy
There are three methods for managing associated objects:
  • Objc_setAssociatedObject sets the associated object
/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 */
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
Copy the code
  • objc_getAssociatedObject
/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 */
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
Copy the code
  • Objc_removeAssociatedObjects (Remove associated objects)
/** * Removes all associations for a given object. * * @param object An object that maintains associated objects. * * Pristine state @Note The main purpose of this function is to make it easy to return an object * to a "pristine state "You should not use this function for general removal of * associations from objects, since it also removes associations that other clients * may have added to the object. Typically you should use \c objc_setAssociatedObject * with a nil value to clear an association. * */ OBJC_EXPORT void objc_removeAssociatedObjects(id _Nonnull object)Copy the code

Summary:

  • Two objects can be linked through the “associate object” mechanism
  • Defining associated objects can specify memory management policies
  • Application scenario: Use associated objects only when other practices (proxy, notification, etc.) are not available. This is hard to bug. But there are also specific application scenarios: for example, the previous demo on controlling the Button response time encountered: attach links

Understand objc_msgSend

First, we need to distinguish between two basic concepts: 1. Static binding: The ability to determine at compile time what functions should be called at runtime. ~ represents languages: C, C++, etc. ~ 2. Dynamic binding: The function to be called is not determined until runtime. Language: OC, swift, etc

OC is a powerful dynamic language, and its dynamic nature is reflected in its powerful Runtime mechanism.

Explanation: In OC, if a message is passed to an object, a dynamic binding mechanism is used to determine which method needs to be called. At the bottom, all methods are plain C functions, but the runtime decides which method to call when an object receives a message, and can even change while the program is running. These features make OC a powerful dynamic language.

Underlying implementation: based on C language functions.
  • The basic function implemented isobjc_msgSend, defined as follows:
void objc_msgSend(id self, SEL cmd, ...) 
Copy the code

This is a variable number of arguments. The first argument represents the receiver, the second argument represents the selector (OC function name), and the subsequent arguments are the arguments passed in the message.

  • Example: Git commit
id return = [git commit:parameter];
Copy the code

The above method is converted to the following OC function at runtime:

id return = objc_msgSend(git, @selector(commit), parameter);
Copy the code

The objc_msgSend function searches the list of methods in the recipient’s class, and if it finds a method with the same name as the selector’s child, it jumps to its implementation code and executes. If the current class is not found, then continue to look up the inheritance system, and wait for the appropriate method to jump, if still not found, then enter the process of message forwarding (the next detailed expansion) to deal with.

However, if you had to iterate through the methods in your class every time you passed a message, all that messaging would add up to a performance drain. So here’s how to optimize OC messaging.

OC optimizations for messaging:
  • Quick mapping table(Cache) optimization:objc_msgSendThere is a cache on the search block, and every OC class has a cache,objc_msgSendCaches the match resultFast Map, so that some of the frequently called methods of this class will appear infast mapYou don’t have to search through the list of methods again and again.
  • Tail call optimization: Principle: When a function with no return value is called at the end of a function, the compiler will automatically reallocate the memory in the stack space, directly release all local variables inside the calling function, store the instruction code needed to call another function, and then directly enter the address of the called function. Benefits: Maximize reasonable allocation of resources and avoid premature stack overflow. (This piece is a bit of the bottom, the above is xiaobian’s personal understanding. If you have a better and deeper insight, welcome to leave a message with us to discuss)

Understand the message forwarding mechanism

First, distinguish two basic concepts: 1. Message passing: Objects normally interpret messages and pass them on (see previous article). 2. Message forwarding: The object cannot read the message and forwards the message.

Complete flow chart of message forwarding:

Process explanation:

  • Step 1: InvokeresolveInstanceMethod: Asks the recipient (the class to which it belongs) if it can add methods to handle unknown selectors. ~(This process is called dynamic method resolution) ~ If yes, the forwarding is complete. If not, go to step 2.
  • Step 2: InvokeforwardingTargetForSelector: asks the receiver if there are other objects that can process the message. If so, forward end, business as usual. If not, go to step 3.
  • Step 3: InvokeforwardInvocationThe run-time system encapsulates the message in the NSInvocation object and gives the receiver another chance.
  • Finally: if the above three steps fail, throw an exception:unrecognized selector sent to instance xxxx

Viii. Use “Method Deployment Technology” to debug “Black Box Method”

Method Swizzling: Replace the Method implementation with another Method implementation. In practice, this technique is often used to add new functionality to an existing implementation. ~

<objc/runtime.h> two common methods:

  • Gets the specified instance method of the given class:
/** 
 * Returns a specified instance method for a given class.
 * 
 * @param cls The class you want to inspect.
 * @param name The selector of the method you want to retrieve.
 * 
 * @return The method that corresponds to the implementation of the selector specified by 
 *  \e name for the class specified by \e cls, or \c NULL if the specified class or its 
 *  superclasses do not contain an instance method with the specified selector.
 *
 * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
 */
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
Copy the code
  • Swap the methods implemented by the two methods:
/** 
 * Exchanges the implementations of two methods.
 * 
 * @param m1 Method to exchange with second method.
 * @param m2 Method to exchange with first method.
 * 
 * @note This is an atomic version of the following:
 *  \code 
 *  IMP imp1 = method_getImplementation(m1);
 *  IMP imp2 = method_getImplementation(m2);
 *  method_setImplementation(m1, imp2);
 *  method_setImplementation(m2, imp1);
 *  \endcode
 */
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
Copy the code

Using these two methods, you can swap a specified method in a specified class. In practice, we would add new functionality to existing methods in this way.

For Example: Swap method implementations For method1 and method2

Method method1 = class_getInstanceMethod(self, @selector(method1:));
Method method2 = class_getInstanceMethod(self, @selector(method2:));
method_exchangeImplementations(method1, method2);
Copy the code

Understand the meaning of “class object”

An Objective-C Class is represented by a Class type, which is essentially a pointer to the objc_class structure. It is defined as follows:

typedef struct objc_class *Class;
Copy the code

You can see his implementation in <objc/ Runtime.h > :

struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; / /! < pointer to metaClass #if! __OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; / /! < parent class const char * _Nonnull name OBJC2_UNAVAILABLE; / /! < class name long version OBJC2_UNAVAILABLE; / /! < Class version information, default is 0 long info OBJC2_UNAVAILABLE; / /! < class information, some bit identifier for run-time use long instance_size OBJC2_UNAVAILABLE; / /! Struct objc_iVAR_list * _Nullable ivars OBJC2_UNAVAILABLE; / /! Struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; / /! Struct objc_cache * _Nonnull Cache OBJC2_UNAVAILABLE; / /! Struct objc_PROTOCOL_list * _Nullable protocols OBJC2_UNAVAILABLE; / /! < protocol linked list #endif} OBJC2_UNAVAILABLE; /* Use `Class` instead of `struct objc_class *` */Copy the code

This structure stores metadata about a class, such as how many methods an instance of the class implements, who the parent class is, and how many instance variables it has. The ISA pointer here points to another class called metaClass. So what is a metaclass? A metaclass is a class of a class object. Or you can put it in a more understandable way:

  1. When you send a message to an object,runtimeIt looks for methods in the list of the object’s classes
  2. When you send a message to a class,runtimeIt looks for methods in the metaclass of the class

Let’s look at a classic picture to understand:

It can be summarized as follows:

  1. eachClassThere is aIsa pointerPoint to a uniqueMeta Class
  2. eachMeta ClasstheIsa pointerThey all point to the topMeta ClasstheMeta ClassisNSObjecttheMeta Class. (includingThe Meta Class NSObjecttheIsa pointerAlso pointingNSObjecttheMeta Class)
  3. eachMeta Classthesuper classThe pointer points to what it wasClasstheSuper ClasstheMeta ClassIt’s on the topNSObjecttheMeta Classthesuper classThe pointer still points to itself)
  4. The top of theNSObject Classthesuper classPoint to thenil

Finally, a special thanks to Effective Objective-C 2.0 chapter 2

QiShare(Simple book) QiShare(digging gold) QiShare(Zhihu) QiShare(GitHub) QiShare(CocoaChina) QiShare(StackOverflow) QiShare(wechat public account)