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