What is the Runtime
As the name implies, Runtime is Runtime, which is a very important feature of Objective-C that differentiates it from static languages like C.
For static languages, function calls are determined at compile time and executed directly after compile. OC is a dynamic language where function calls become message sending, and it is not known at compile time which function to call. Runtime is all about figuring out how to find specific methods to call at Runtime.
#if ! OBJC_TYPES_DEFINED /// An opaque type that represents an Objective-C class. typedef struct objc_class *Class; /// Represents an instance of a class. struct objc_object { Class _Nonnull isa OBJC_ISA_AVAILABILITY; }; /// A pointer to an instance of a class. typedef struct objc_object *id; #endif /// An opaque type that represents a method selector. typedef struct objc_selector *SEL; /// A pointer to the function of a method implementation. #if ! OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... * /); #else typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...) ; #endif struct objc_protocol_list { struct objc_protocol_list *next; long count; Protocol *list[1]; }; struct objc_category { char *category_name OBJC2_UNAVAILABLE; char *class_name OBJC2_UNAVAILABLE; struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; struct objc_method_list *class_methods OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; struct objc_ivar { char *ivar_name OBJC2_UNAVAILABLE; char *ivar_type OBJC2_UNAVAILABLE; int ivar_offset OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; struct objc_ivar_list { int ivar_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; struct objc_method_list { struct objc_method_list *obsolete OBJC2_UNAVAILABLE; int method_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_method method_list[1] OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE; struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if ! __OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; 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; #endif } OBJC2_UNAVAILABLE;Copy the code
How do I call a method in OC
The ISA pointer can be used to find the class object to which the instance object belongs. The class contains the list of instance methods. At compile time, a unique identifier SEL for the method is generated based on the method name. IMP is a function pointer to a function implementation. The simplified process is: instance -> class -> methodList -> SEL -> IMP -> implement the function
When an instance method is called, the class object corresponding to the instance is found through the ISA pointer and queried in the cache method list and method list. If it cannot be found, the parent class is queried according to the super_class pointer until the root class (NSObject).
2. When the class method is called, the metaclass corresponding to the instance is found by isa pointer and queried in the list of cached methods and methods. If it cannot be found, the metaclass of the parent class is queried according to the super_class pointer until the metaclass of the root class.
3. If no message is found, the message is forwarded.
At the heart of the Runtime is the objc_msgSend function, which sends SEL messages to the class to find matching IMPs. The following diagram depicts the memory layout of an object.
Message forwarding mechanism
The simplified figure is as follows:Message forwarding consists of three steps:
-
Message dynamic processing phase
-
Fast message forwarding phase
-
General message forwarding phase
Message dynamic processing phase
Check that the class implements the resolveInstanceMethod: method, and then check that methods are dynamically added to the class.
- (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(run)) {
SEL readSEL = @selector(readBook);
Method readM = class_getInstanceMethod(self, readSEL);
IMP readImp = method_getImplementation(readM);
const char *type = method_getTypeEncoding(readM);
return class_addMethod(self, sel, readImp, type);
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)name
{
return [super resolveClassMethod:name];
}
Copy the code
The essence of dynamic method resolution: dynamically adding methods to the class within the method. After adding the class object inside the instance method list and cache method list there is this method, return true, the system will automatically traverse the inheritance chain again to find imp.
Fast message forwarding phase
There may be other objects inside the object that can respond to this method, so this method forwards SEL to other objects inside the object that can respond to this method.
- (id)forwardingTargetForSelector:(SEL)aSelector {
return [[Car alloc] init];
}
Copy the code
General message forwarding phase
Conventional forwarding is essentially the same as fast forwarding, which is to switch the object receiving the message. However, conventional forwarding is more complicated. Fast forwarding only needs to return an object that can respond, and conventional forwarding also needs to manually switch the response method to the response object.
The third step consists of two steps:
1. Create a signature
2. Forwarding method
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { NSString *sel = NSStringFromSelector(selector); if([sel isEqualString:@"run"]) { return [NSMethodSignature signatureWithObjcTypes:"v@:"]; } return [sunper methodSignatureForSelector:selector]; } - (void)forwardInvocation:(NSInvocation *)invocation { SEL selector = [invocation selector]; Car * Car = [[Car alloc] init]; if([car respondsToSelector:selector]){ [invocation invokeWithTarget:car]; }}Copy the code
Summary: At each of the three steps, the message recipient still has the opportunity to process the message. At the same time, processing becomes more expensive later in the process, and it is best to process the message in the first step so that the Runtime caches the result after processing and then sends the same message again to improve processing efficiency. The second step is also less costly to transfer the message to the recipient than to proceed to the normal forwardInvocation, and if the final step is the forwardInvocation, the full NSInvocation object needs to be handled.
The Category principle
struct objc_category {
char *category_name OBJC2_UNAVAILABLE;
char *class_name OBJC2_UNAVAILABLE;
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
Copy the code
You can also see from the category definition that a category can add instance methods, class methods, protocols, and attributes to a class and cannot add instance variables to a class.
Category and associated objects
You can’t add instance variables to a category in a category. But most of the time we need to add the value associated with the object in a category:
#import "MyClass.h"
@interface MyClass (Category1)
@property(nonatomic,copy) NSString *name;
@end
----------
#import "MyClass+Category1.h"
#import <objc/runtime.h>
@implementation MyClass (Category1)
+ (void)load
{
NSLog(@"%@",@"load in Category1");
}
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self,
"name",
name,
OBJC_ASSOCIATION_COPY);
}
- (NSString*)name
{
NSString *nameObject = objc_getAssociatedObject(self, "name");
return nameObject;
}
@end
Copy the code
Methods in categories
When methodA in a category has the same name as methodA in the original class, the method in the category replaces the method in the original class, but does not “completely replace” the existing method in the original class. There are two methodA methods in the class list, which means that the category method is placed first and the original method is placed second. This is because the runtime looks for methods in order of the list of methods, and returns as soon as it finds a method with the corresponding name, not knowing that there may be other methods with the same name behind them.
The load and initialize
- load
The load method is called when a class or class is loaded at startup;
The load method in —— does not override the load method of this class.
Void autoRelease pool void autoRelease pool void autoRelease pool When the Runtime calls +(void)load, the application has not yet created its autoRelease pool, so any code that needs to use the autoRelease pool will get an exception. It is important to note that objects placed in +(void) loads should be alloc and cannot be released using autoRelease.
2, the initialize
Initialize is called before the first method of the class or its subclass is called. The initialize method in —— overrides the initialize method of this class.
The initialize functions are called in either superClass-> class or superClass-> category
If the class is a subclass:
A, if the + (void)initialize message is not implemented in a subclass, the parent class’s implementation will be called. + (void) Initialize of the parent class may be called multiple times.
B, the + (void)initialize message in a subclass will call the parent’s implementation first and then the subclass’s implementation.
C, if the class contains a class (extending catagory) that overrides the initialize method, the initialize implementation of the class will be called, and the original class’s implementation will not be called. The order of child and parent calls remains the same.
This mechanism is the same as the other methods on NSObject (except for the + (void)load method), which means that if the original class contains the same method implementation as the class’s classification, the method of the original class is hidden and cannot be called.
Load and Initialize usage scenarios
4. Application scenarios
- load
The load Method is thread-safe and must be called only once, usually in exchange for the Method Swizzle.
- initialize
The Initialize method is used to assign values to objects that are not easy to initialize at compile time, or to initialize static constants
The principle of KVO
The full name of KVO is key-value Observing, commonly known as “key-value monitoring”, which can be used to monitor the change of an object’s attribute Value.
- (void)viewDidLoad { [super viewDidLoad]; Person *p1 = [[Person alloc] init]; Person *p2 = [[Person alloc] init]; p1.age = 1; p1.age = 2; p2.age = 2; / / the self monitoring the age attribute of the p1 NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [p1 addObserver:self forKeyPath:@"age" options:options context:nil]; p1.age = 10; [p1 removeObserver:self forKeyPath:@"age"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {NSLog(@" monitor %@ change %@", object, keyPath,change); } Person: 0x604000205460> {kind = 1; new = 10; old = 2; }Copy the code
After the listener is added, the value of the age property is notified to the listener when it changes and the listener’s observeValueForKeyPath method is executed.
Underlying implementation principles of KVO
1. Memory structure diagram of person objectWhen the Setage method is called, the Person class object is first found through the ISA pointer in the P2 object, and then the Setage method is found in the class object. Then find the corresponding implementation of the method.
2. Memory structure diagram of Person with KVO added
The isa pointer of p1 has already pointed to NSKVONotifyin_Person after KVO listening. NSKVONotifyin_Person is actually a subclass of Person, so that means its superclass pointer points to Person. NSKVONotifyin_Person is generated by Runtime. Therefore, when p1 object calls setage method, it must find NSKVONotifyin_Person according to P1 ISA, and find the method and implementation of setage in NSKVONotifyin_Person.
The setage method in NSKVONotifyin_Person actually calls the Fundation C function _NSsetIntValueAndNotify. What nsSetintValueAndNotify does internally is call willChangeValueForKey to change the method, then call the setage method of the parent class to assign a value to a member variable, and finally call didChangeValueForKey to change the method. The listener’s listener method is called in didChangeValueForKey, which ends up in the listener’s observeValueForKeyPath method.
Note: NSKVONotifyin_Person rewrites the class internal implementation roughly as
Return class_getSuperclass(object_getClass(self)); }Copy the code
Apple does not want to expose the NSKVONotifyin_Person class, and does not want us to know the internal implementation of NSKVONotifyin_Person, so the internal rewrite of the class class, directly return the Person class, so when the outside world calls p1 class object method, Is the Person class.
Common KVO related questions
1. How does iOS implement KVO for an object? (What is the nature of KVO?)
——— When an object uses a KVO listener, iOS changes the isa pointer to a new subclass created dynamically by Runtime with its own set implementation. The set implementation internally calls the willChangeValueForKey method, the original setter implementation, didChangeValueForKey method, And internal didChangeValueForKey method will be called the listener observeValueForKeyPath: ofObject: change: context: monitoring method.
2. How to manually trigger KVO answer. KVO is automatically triggered when the value of the monitored property is modified. If we want to trigger KVO manually, we need to call the willChangeValueForKey and didChangeValueForKey methods ourselves to trigger KVO manually without changing the property value, and both methods are necessary.
Person *p1 = [[Person alloc] init]; P1. The age = 1.0; NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [p1 addObserver:self forKeyPath:@"age" options:options context:nil]; [p1 willChangeValueForKey:@"age"]; [p1 didChangeValueForKey:@"age"];Copy the code