Chapter 2: Objects, messages, runtime

No.6: Understand the concept of “attributes”

  • OC treats instance variables as special variables that store offsets, managed by class objects, which are looked up at run time

    This always finds the right place whenever instance variables are accessed, and enables the runtime to add instance variables to the class

  • @property generates attributes based on the memory management semantics we specified, and also generates access methods, noting weak and unsafe_unretained

    • Weak: When the object to which the attribute points is destroyed, the attribute value is also empty
    • Unsafe_unretained: The property value remains intact when the object pointed to is destroyed
  • At sign synthesize creates an alias for the property we define

  • @dynamic tells the compiler not to create access methods for specifying properties, but to do so manually

  • Atomic and nonatomic. Atomic uses synchronous locks to ensure that each read value is valid, but they are not thread-safe and are expensive

    Therefore, we should use nonatomic to avoid unnecessary overhead

No.7: Try to access instance variables directly from within the object

  • Access via attributes vs direct access

    We should access instance variables directly when we read them, and through properties when we set instance variables

  • Direct access to the points that need attention

    • It is fast to directly access the memory without forwarding messages from the Runtime
    • Bypassing memory management semantics, such as direct access to a property declared as copy under ARC, the property is not copied, only the new value is retained and the old value is released
    • Don’t trigger the KVO
    • Cannot add breakpoint debug to access methods
  • We should call the initialization method directly, because subclasses may override the setting method

  • Lazy-loaded properties must be accessed through properties or their lazy-loaded methods will never be executed

No.8: Understand the concept of “object equality”

  • IsEqual in NSObject: Used to determine equality. The internal implementation rule is that two objects are equal if and only if the pointer values are exactly the same

    If isEqual: determines that two objects are equal, the hash values of the two objects also want to be equal

    If the hash value of two objects is equal, the two objects are not necessarily equal

    NSString *a1 = @"aaa";
    NSString *a2 = [NSString stringWithString:@"aaa"];
    NSString *a3 = @"aaaaaa";
    NSLog(@"%p, %p, %lu", a1, &a1, (unsigned long)a1.hash);
    NSLog(@"%p, %p, %lu", a2, &a2, (unsigned long)a2.hash);
    NSLog(@"%p, %p, %lu", a3, &a3, (unsigned long)a3.hash);
    
    // 2021-10-04 22:18:47.940473+0800 EOC[10148:828685] 0x100004018, 0x7ffeefbff458, 516200022
    // 2021-10-04 22:18:47.940875+0800 EOC[10148:828685] 0x100004018, 0x7ffeefbff450, 516200022
    // 2021-10-04 22:18:47.940908+0800 EOC[10148:828685] 0x100004038, 0x7ffeefbff448, 8835314326513740
    Copy the code
  • About hash methods

    • Called only when NSSet adds a new value and NSDictionary adds a new key, because both need to be unique
    • The hash method can be overridden by us. When overriding the hash method, we need to pay attention to the efficiency issue and do tradeoff between collision frequency and operational complexity
  • Override isEqual: and hash methods instead of blindly checking each attribute individually

No.9: Hide implementation details with “family pattern”

  • OC system frameworks commonly use family patterns, hiding implementation details behind simple public excuses, such as UIButton and most collections. We can implement our own family patterns using factory patterns

    We should use the isKindOfClass: method instead of ==

    NSArray *array = @[@1, @2, @3]; NSLog(@"%@", array.class); NSLog(@"%d", array.class == NSArray.class); NSLog(@"%d", [array isKindOfClass:NSArray.class]); // 2021-10-04 22:51:05.613016+0800 EOC[10319:849813] __NSArrayI // 2021-10-04 22:51:05.613283+0800 EOC[10319:849813] 0 // 21-10-04 22:51:05.613314+0800 EOC[10319:849813] 1Copy the code
  • In general, do not inherit from the common abstract base class of a class family, which is often complex and requires reading its development documentation

No.10: Use associated objects to store custom data in existing classes

  • Associative objects can specify memory management semantics, concatenating two objects, but only when other practices are not available, because associative objects may introduce bugs that are difficult to debug

No.11: Understand the role of objc_msgSend

  • Each method of an OC object is converted to something like the following via objc_msgSend

    In the internal implementation, objc_msgSend is tail-call optimized to prevent premature stack overflow, so we can see objc_msgSend in the debug backtrace

    <return_type> class_selector(id self, SEL _cmd, ...)
    Copy the code

No.12: Understand the message forwarding mechanism

  • The message forwarding process is triggered when the object cannot respond to a Selector

    • The Runtime dynamic method resolution allows you to add a method to a class when it is needed

    • Dynamic method parsing fails, can forward message to other object, failure return nil

    • Enable the full message forwarding mechanism, encapsulating the selector, target, and params of the message to create NSIvocation objects

      According to the hierachy each send a message until the NSObject, if still won’t do, trigger the doesNotRecognizeSelector: throw an exception

  • CALayer implements getters and setters for each dynamically added property in the resolveInstanceMethod

    Classes like CALayer are called key-value coding Compliant.

No.13: Debug the black box method with Method Swizzling

  • With Method Swizzling, you can add methods to a class and replace existing methods at run time

    #import <objc/runtime.h> @implementation UIViewController (Tracking) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(xxx_viewWillAppear:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); // When swizzling a class method, use the following: // Class class = object_getClass((id)self); / /... // Method originalMethod = class_getClassMethod(class, originalSelector); // Method swizzledMethod = class_getClassMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); }}); } #pragma mark - Method Swizzling - (void)xxx_viewWillAppear:(BOOL)animated { [self xxx_viewWillAppear:animated]; NSLog(@"viewWillAppear: %@", self); } @endCopy the code

No.14: Understand class objects

  • Objc_object and objc_class

    typedef struct objc_object {
    		Class isa;
    } *id;
    
    typedef struct objc_class *Class; 
    struct objc_class {
        Class isa;
        Class super_class;
        const char *name;
        long version;
        long info;
        long instance_size;
        struct objc_ivar_list *ivars;
        struct objc_method_list **methodLists; struct objc_cache *cache;
        struct objc_protocol_list *protocols;
    };
    Copy the code
  • Inheritance relationships

    Instance methods are stored in class objects and class methods are stored in metaclasses

  • Do not compare classes directly with object.class. Instead, use isKindOfClass: and isMemberOfClass:, because some class objects may implement message forwarding

    IsKindOfClass: Determines whether an object is an instance of a class or a derived class

    IsMemberOfClass: Determines whether the object is an instance of a particular class

    NSMutableDictionary *dict = [NSMutableDictionary new];
    [dict isMemberOfClass:[NSDictionary class]]; ///< NO
    [dict isMemberOfClass:[NSMutableDictionary class]]; ///< YES
    [dict isKindOfClass:[NSDictionary class]]; ///< YES
    [dict isKindOfClass:[NSArray class]]; ///< NO
    Copy the code