Before we begin, let’s take a look at classes and objects in OC
This is a classic class diagram. Let’s take a quick look at this diagram
-
First, when we create an instance object, we copy the member variables of the class that the instance object belongs to, but we do not copy the methods defined by the class
-
When we send a message to an instance object, we will use the ISA pointer in the instance object to find the corresponding class. We will look for it in the method cache of the class. If there is no hit, we will look for it in the method list. You go through the same process and you go up until you find the root class which is NSObject, and the thing to notice here is that the super class pointer in NSObject points to nil
-
So how does a class object look up a method when it receives a message? Similarly, in the class object will also isa pointer, it points to the class object for metaclasses, is also the first metaclass approach to find in the cache, if missed, then go to the list of methods to find, if has not hit, will be through the super class a metaclass pointer to its parent the metaclass to find, in the same process have been up to find, Until the root metaclass is found, one thing to note here is that in the metaclass, every metaclass’s ISA pointer points to the root metaclass, and the super class pointer to the root metaclass points to the root NSObject, which means that when a class method is not implemented, but there is an instance method implementation of the same name in the root class, This is when the instance method of the same name is called
Personally, I think the relationship between class and object can be well understood as long as we understand this diagram thoroughly
After the appetizers, we moved on to the main meal
forward
Come on out, flow chart!
-
For instance methods, the system first calls resolveInstanceMethod, which takes a SEL and returns a BOOL, to tell the system whether to resolve the implementation of the instance method. If YES, the message has been processed. If NO is returned, the system will give us a second chance to process the message
-
System will call forwardingTargetForSelector: this method, the method of parameter is a selector (SEL), the return value is a type of id, equivalent to tell the system the selector is concrete which object to deal with, if we forward specifies a target, The system will forward the message to our forwarding target and end the current forwarding process at the same time. If we still do not return a forwarding target in the second chance, the system will give us the third and last chance to process the message
-
System will call methodSignatureForSelector: this method, the method of parameter is a selector (SEL), the method’s return value is a methodSignature, this method signature is actually the type of selector of the method, the return value and parameters of a wrapper, at this time, If returned to a method signature, the system will then call forwardInvocation: method, if can handle the message has to deal with, if methodSignatureForSelector: returns an empty, or forwardInvocation: unable to deal with, Then the message will be marked as unable to process, usually we common one of the crash is unable to recognize the selector, in fact, the last step is still unable to process
So here’s the code
+ (BOOL)resolveInstanceMethod:(SEL) SEL {// if the call makes ourtestmethodsif (sel == @selector(test)) {
NSLog(@"resolveInstanceMethod");
return NO;
}else {
return [super resolveInstanceMethod:sel];
}
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector"); // Return nil to proceed to step 3return nil;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
NSLog(@"methodSignatureForSelector");
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}else {
return [super methodSignatureForSelector:aSelector];
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation");
}
Copy the code
-
First we create a class that inherits from NSObject, in which we declare a test method, but don’t implement it, and then we implement the message forwarding method
-
In resolveInstanceMethod: first method, if the selector is test, we print the name of this method, and returns NO into forwardingTargetForSelector: method
-
In the method forwardingTargetForSelector: we print the method name, and then returns nil, so can enter methodSignatureForSelector: method
-
In methodSignatureForSelector: approach, we still judge whether the test, if it is, we print the method name and returns a correct method signature, preach about this method and can see my previous article, portal under it
-
Finally, the forwardInvocation: method is implemented
Let’s take a look at the print
2018-12-13 12:43:07.287974+0800 RuntimeDemo[41443:8663918] resolveInstanceMethod 2018-12-13 12:43:07.288012+0800 RuntimeDemo [41443-8663918] forwardingTargetForSelector 2018-12-13 12:43:07. 288019 + 0800 RuntimeDemo (41443-8663918) MethodSignatureForSelector 12:43:07 2018-12-13. 288036 + 0800 RuntimeDemo forwardInvocation [41443-8663918]Copy the code
Dynamic addition method
Without further ado, code first
void testImp (void) {
NSLog(@"test invoke"); } + (BOOL)resolveInstanceMethod:(SEL) SEL {// if the call makes ourtestmethodsif (sel == @selector(test)) {
NSLog(@"resolveInstanceMethod"); // Dynamic addtestMethod that implements class_addMethod(self, @selector()test), testImp, "v@:");
return YES;
}else {
return[super resolveInstanceMethod:sel]; }}Copy the code
-
First of all, we need to dynamically add methods in resolveInstanceMethod: method implementation
-
Call class_addMethod, where the first argument is the class to which the method is added, here we write the current class, the second argument is the method selector to which the method implementation is added, and the third argument is the IMP to which the method implementation is added. I implemented the method above, printing a sentence, and the last one is the method character pointer, An explanation of this can be found in my last article, where the portal is at the bottom
Then we run it and print the following
2018-12-13 13:06:33.377111+0800 RuntimeDemo[41479:8670877] resolveInstanceMethod
2018-12-13 13:06:33.377156+0800 RuntimeDemo[41479:8670877] test invoke
Copy the code
- We have already added the method implementation, so we need to return YES, and we won’t go to the method below
Dynamic method parsing
@dynamic
-
So let’s first look at the at sign dynamic keyword
-
When the property we declare is marked as @dynamic in the implementation, its equivalent set and get methods are added at runtime, rather than being declared at compile time
-
Dynamic runtime languages defer function decisions to runtime, essentially adding running functions to methods at runtime
-
Compile-time languages make function decisions at compile time
portal
IOS Exploration: Basic data structures for Runtime
Github
Demo