OC is called dynamic runtime language. The main reason is that there are two features: runtime and polymorphism.
runtime
Runtime, also known as runtime, is an underlying c language api, which is one of the core of iOS. OC is a dynamic runtime language, it will put some work on the code runtime to deal with, rather than compile time, such as dynamic traversal attributes and methods, dynamic addition of attributes and methods, dynamic modification of attributes and methods.
To understand runtime, we first need to understand its core, messaging.
message passing
Messages are not bound to method practices until run time.
An instance object invokes an instance method, such as [obj doSomething]; the compiler sends objc_msgSend(obj, @selector(doSomething),); and
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
runtime runs as follows:
- First, class is found by calling isa of the object.
- Find the method in the method_list of the class. If it is an instance object, then go to the method list of the class of the instance object. If it is a class object that calls a class method, then go to the method list of the metaclass.
- If you can't find it in the class, go ahead and look for it in its superClass.
- Once you find the function doSomething, you execute its IMP implementation.
Here's the structure of object s, class es and method s:
//object struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; //class struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; //Method list 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; //Method struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; }
Class object (objc_class)
Classes in OC are represented by Class, which is actually a pointer to the objc_class structure.
//object struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; //class struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; //Method list 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; }
Looking at the object structure and the class object structure, we can see that there is an ISA pointer inside. The isa pointer of the object points to the class, the ISA pointer of the class points to the metaClass, and the metaClass is also the class. The isa pointer of the metaClass finally points to the root metaClass, and the ISA pointer of the root metaClass points to itself, and finally forms a closed loop.
You can see that there is a method Lists in the class structure, which explains that the member methods mentioned above are recorded in class method-list and the class methods are recorded in metaClass. That is, the information of Instance-object is recorded in class-object, while the information of class-object is recorded in meta-class.
There is an ivars pointer in the structure pointing to the objc_ivar_list structure, which is the list of attributes of this class. Because the compiler compiles in the order of parent class, subclass and classification, this is why categorization cannot add attributes, because classes are registered in runtime at the time of compilation, and the memory size of the attribute list objc_ivar_list and instance_size have been determined. Runtime calls class_setIvarLayout and class_setWeakIvarLayout to handle strong and weak references. Attributes can be added to classifications through runtime's Association attributes (because there is an instance Properties in the category structure, which will be discussed later). Because the compilation order is parent class, subclass, classification, so the order of message traversal is classification, subclass, parent class, FIFO.
The objc_cache structure is a very useful method cache, which caches frequently invoked methods and improves traversal efficiency. method_name is used as key and method_imp is saved as value.
Method(objc_method)
The structure is as follows:
//Method struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; }
You can see that there is a SEL and IMP in it. Here is the difference between them.
SEL is the OC representation of selector, and its data structure is typedef struct objc_selector* SEL; it is a c string mapped to a method; unlike function pointer, function pointer directly saves method address, SEL is only a number; it is also the key in objc_cache.
ps. This also brings a disadvantage, function overloading is not applicable, because function overloading is the same method name, parameter name is different, but SEL only remember the method name, no parameters, so it can not distinguish different methods.
ps. In different classes, the same method name and method selector are the same.
IMP is a function pointer, and the data structure is typedef id (IMP)(id,SEL,**); the method address is saved, generated by the compiler binding, and the final method execution code is determined by IMP. IMP points to the implementation of the method, and a set of IDS and SELs can determine the only implementation.
With the intermediate process of SEL, we can do some intermediate operations for a number and method implementation, that is to say, we can point to different function pointers for a SEL, so that we can complete a method name to execute different function bodies at different times. In addition, SEL can be passed as a parameter to different classes to execute, that is, some of our businesses only know the method name but need to let different classes execute according to different circumstances. Personally, message forwarding takes advantage of this intermediate process.
How does runtime find the corresponding IMP through selector?
The list of instance methods in class objects and the list of class methods in metaclass objects are described above. The names, parameters and implementations of methods are recorded in the list. The selector is essentially the method name, or SEL, through which the method name can be found in the list to implement the method.
When looking for IMP, runtime offers two approaches:
- IMP class_getMethodImplementation(Class cls, SEL name);
- IMP method_getImplementation(Method m);
For the first method, both instance method and class method call this method to find IMP, but different from the first parameter, the parameter passed by instance method is [obj class], and the parameter passed by class method is objc_getMetaClass ("obj").
For the second method, the only parameters passed in are Method, and the method to distinguish the classification method from the instance method is to encapsulate the function of Method. Class method: Method class_getClass Method (Class cls, SEL name); Example method: Method class_getInstance Method (Class cls, SEL name);
Category(objc_category)
category is a pointer to a structure pointing to a classification. The structure is as follows:
struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; };
Name: class_name, not category_name. cls: Class objects to be extended are not defined during compilation, but correspond to corresponding class objects by name during Runtime phase. instanceMethods: A list of all instance methods added to classes in category. classMethods: A list of all added class methods in category. Protocol: A list of all protocols implemented by category. Instance Properties: Represents all the properties in Category, which is why we can add instance variables through objc_setAssociatedObject and objc_getAssociatedObject, but this is different from general instance variables.
As can be seen from the above structure, categorization can add instance methods, class methods, protocols, and attributes through associated objects, but can not add member variables.
runtime message forwarding
As mentioned earlier, when a method is executed, i.e. sending a message, it will go to the relevant method list to find the corresponding method to implement IMP. If the root class is not found, it will enter the message forwarding stage. Here are the last three gatherings of message forwarding.
- Dynamic Method Analysis
- Alternate Recipient
- Full message forwarding
Dynamic Method Analysis
First, when no method implementation is found in the root class, runtime calls + resolveInstanceMethod: or + resolveClassMethod:, giving you the opportunity to provide a function implementation. If you add a function and return yes, the runtime will go back to the process of sending messages.
An example of implementing a dynamic method parsing is as follows:
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //Executing foo functions [self performSelector:@selector(foo:)]; } + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(foo:)) {//If the foo function is executed, it is dynamically parsed to specify a new IMP class_addMethod([self class], sel, (IMP)fooMethod, "v@:"); return YES; } return [super resolveInstanceMethod:sel]; } void fooMethod(id obj, SEL _cmd) { NSLog(@"Doing foo");//New foo function }
You can see that although we did not implement the foo function, we dynamically added a new function to implement the fooMethod through class_addMethod and returned yes.
If you return to no, you go to the next step, - forwarding Target ForSelector:.
Alternate Recipient
Examples of implementation are as follows:
#import "ViewController.h" #import "objc/runtime.h" @interface Person: NSObject @end @implementation Person - (void)foo { NSLog(@"Doing foo");//Person's foo function } @end @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //Executing foo functions [self performSelector:@selector(foo)]; } + (BOOL)resolveInstanceMethod:(SEL)sel { return NO;//Go back to NO and forward to the next step } - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(foo)) { return [Person new];//Return the Person object and let the Person object receive the message } return [super forwardingTargetForSelector:aSelector]; } @end
You can see that we forwarded the foo function of the current viewController to the foo function of Person to execute through the - forwarding Target ForSelector: method.
If the unknown message cannot be processed at this step, the next complete message forwarding step is entered.
Full message forwarding
First, the - Method Signature ForSelector: message is sent to get the parameters of the function and the type of the return value. If - Method Signature ForSelector: returns nil, runtime sends a - doseNotRecognize Selector message, and the program hangs; if a function label is returned, runtime creates an NSInvocation object and sends - forward Invocation: message to the target object.
The implementation examples are as follows:
#import "ViewController.h" #import "objc/runtime.h" @interface Person: NSObject @end @implementation Person - (void)foo { NSLog(@"Doing foo");//Person's foo function } @end @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //Executing foo functions [self performSelector:@selector(foo)]; } + (BOOL)resolveInstanceMethod:(SEL)sel { return NO;//Go back to NO and forward to the next step } - (id)forwardingTargetForSelector:(SEL)aSelector { return nil;//Return to nil and proceed to next forwarding } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) { return [NSMethodSignature signatureWithObjCTypes:"v@:"];//Sign, enter forward Invocation } return [super methodSignatureForSelector:aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { SEL sel = anInvocation.selector; Person *p = [Person new]; if([p respondsToSelector:sel]) { [anInvocation invokeWithTarget:p]; } else { [self doesNotRecognizeSelector:sel]; } } @end
By signing, runtime generates an anInvocation object, which is sent to forward Invocation:, and we forward Invocation: in which the Person object executes the foo function.
This is the third function forwarding process of runtime.
Better Late Than Never!
Efforts are made to ensure that opportunities are not missed when they come.
Encourage each other!