We analyzed the dynamic method analysis in the previous chapter “message search”, in order to better grasp the specific process, we then directly source tracking.
Let’s start with the _class_resolveMethod method.
void _class_resolveMethod(Class cls, SEL sel, id inst) { if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
Copy the code
} The general process is as follows:
If it’s not a metaclass, call _class_resolveInstanceMethod to dynamically resolve the object method. If it’s a metaclass, Then call _class_resolveClassMethod for class method dynamic parsing to complete the class method dynamic parsing, query the IMP in CLS again, if not found, then a dynamic parsing of object method 1.1 object method dynamic parsing we first analyze the object method dynamic parsing, Let’s go straight to the _class_resolveInstanceMethod method:
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst) { if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/initialize/, YES/cache/, NO/resolver/)) { // Resolver not implemented. return; }
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); }}Copy the code
} The general process is as follows:
+(BOOL)resolveInstanceMethod:(SEL) SEL method (CLS ->ISA()) If a +(BOOL)resolveInstanceMethod (SEL) method is currently implemented, then the SEL method is manually called by objc_msgSend. +(BOOL)resolveInstanceMethod (SEL) SEL, resolveInstanceMethod (SEL) SEL, resolveInstanceMethod (SEL) SEL But did not find the IMP log
The _class_resolveClassMethod () method is used as an example.
static void _class_resolveClassMethod(Class cls, SEL sel, id inst) { assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { // Resolver not implemented. return; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; bool resolved = msg(_class_getNonMetaClass(cls, inst), SEL_resolveClassMethod, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything? _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES" ", but no new implementation of %c[%s %s] was found", cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); }}Copy the code
} The general process is as follows:
+(BOOL)resolveClassMethod:(SEL) SEL class method :(SEL) SEL class method :(CLS – because CLS is a metaclass, ResolveClassMethod (SEL) : SEL (BOOL)resolveClassMethod (SEL) : SEL (BOOL)resolveClassMethod (SEL) _class_getNonMetaClass is called, and then we look for the IMP in the CLS again. If the IMP found it, we’ll print a log that dynamically parses the object method successfully. If the IMP didn’t find it, +(BOOL)resolveClassMethod (SEL) SEL, and return YES, but do not find imp log
One thing to note here is that if we try self instead of objc_getMetaClass(“LGPerson”) in the above example, this will cause the +(BOOL)resolveInstanceMethod (SEL) SEL method to be called, The problem actually happens at the class_getMethodImplementation method, which internally calls the _class_resolveMethod method, and our CLS passes self, So it will go again +(BOOL)resolveInstanceMethod:(SEL) SEL
If the CLS is a metaclass, that is, if the CLS is a class method that is dynamically resolved, the following source code is available:
_class_resolveClassMethod(cls, sel, inst); // If (! lookUpImpOrNil(cls, sel, inst, NO/initialize/, YES/cache/, NO/resolver/)) {// object method resolver _class_resolveInstanceMethod(CLS, sel, inst); } _class_resolveClassMethod (resolveclassmethod ());}
We know from this flow chart that the metaclass ultimately inherits from the root metaclass, which in turn inherits from NSObject, so that means that class methods stored in the root metaclass are equivalent to object methods stored in NSObject. When lookUpImpOrNil is executed, the system recursively looks for a list of methods on the parent of the metaclass. Because the metaclass and root metaclass are automatically generated, we can’t write them directly. For NSObject, we can use categories to achieve uniform dynamic resolution of class methods, but only if the class itself does not implement the resolveClassMethod method:
This explains why _class_resolveClassMethod takes another step in the object method resolution process.
What happens next in the message lookup process if we don’t have dynamic method resolution?
// No implementation found, and method resolver didn’t help. // Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
Copy the code
The lookUpImpOrForward source code returns _objc_msgForward_impcache when dynamic parsing fails. Objc-msg-arm64.s assembler source objC-msG-arm64.s assembler source objC-msG-arm64
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
Copy the code
We can see that __objc_msgForward_impcache jumps to __objc_msgForward, and we get no useful information from __objc_msgForward. Is that when the trail goes cold? We have a meeting in front of the process, if found imp, cache filling and log printing, we might as well find the printed log file to see if there will be the content we need.
static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) { #if SUPPORT_MESSAGE_LOGGING if (objcMsgLogEnabled) { bool cacheIt = logMessageSend(implementer->isMetaClass(), cls->nameForLogging(), implementer->nameForLogging(), sel); if (! cacheIt) return; } #endif cache_fill (cls, sel, imp, receiver); }
bool logMessageSend(bool isClassMethod, const char *objectsClass, const char *implementingClass, SEL selector) { char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
Copy the code
} Here we can clearly see where the log file is stored and how it is named:
Here’s another caveat:
ObjcMsgLogEnabled will only be logged if objcMsgLogEnabled is true. We will search directly for where this value appears:
Obviously, by calling the instrumentObjcMessageSends to achieve print on and off. Here’s a simple test:
Let’s run it and go to /private/ TMP:
Let’s open this file:
We saw the familiar resolveInstanceMethod, but after that there are two methods: that we don’t have explored before forwardingTargetForSelector and methodSignatureForSelector. Then there will be print of doesNotRecognizeSelector method, at this time Xcode console print as follows:
We can see that forwarding occurs within the CoreFoundation framework. We are still as usual, will be subject to the official documentation, check the forwardingTargetForSelector and methodSignatureForSelector.
First forwardingTargetForSelector:
ForwardingTargetForSelector official definition is returned to the IMP’s message directed to the first object was not found, the said person is in this way can realize the civet cats in prince, can’t find the IMP, isn’t it, I send this message to other objects to deal with is not just a matter of? Let’s go straight to code:
- (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@”%s — %@”,func,NSStringFromSelector(aSelector)); if (aSelector == @selector(saySomething)) { return [LGTeacher alloc]; } return [super forwardingTargetForSelector:aSelector]; } here we return [LGTeacher alloc] directly, let’s run it:
We send “saySomething” to LGStudent, and LGTeacher responds to the message. About forwardingTargetForSelector, apple also gives some hints:
If an object implements (or inherits) this method, and returns a non-nil (and non-self) result, that returned object is used as the new receiver object and the message dispatch resumes to that new object. (Obviously If you return self from this method, the code would just fall into an infinite loop. If an object implements or inherits this method and then returns a result that is not null (not self), the return value is treated as the new message recipient object and the message is forwarded to that object. If you return self in this method, then obviously an infinite loop will occur. If you implement this method in a non-root class, Invoking Super’s if your class has nothing to return for the given selector then you should return the result of invoking Super’s implementation. If you implement this method in a non-base class that has nothing to return, then you need to return the implementation of the parent class. Is the return [super forwardingTargetForSelector: aSelector]; . This method gives an object a chance to redirect an unknown message sent to it before the much more expensive forwardInvocation: machinery takes over. This is useful when you simply want to redirect messages to another object and can be an order of magnitude faster than regular forwarding. It is not useful where the goal of the forwarding is to capture the NSInvocation, or manipulate the arguments or return value during the forwarding. This method gives the object a chance to redirect unknown messages sent to it before the more expensive forwardInvocation: mechanical invocation. This is useful when you just want to redirect a message to another object and do it one order of magnitude faster than normal forwarding. This feature is useless in cases where the goal of the forwarding is to capture the NSInvocation or manipulate the parameters or return values during the forwarding. From the above official document definition, we can clarify the thinking:
ForwardingTargetForSelector is a kind of fast forward process, it directly to other objects in response to the news of the unknown. ForwardingTargetForSelector cannot return to self, will otherwise fall into infinite loop, because return self again go back to the object of the current instance to go one time news search process, obviously will come forwardingTargetForSelector again. ForwardingTargetForSelector applies to forward the message to other objects can response to unknown message, what does that mean? Is the content must eventually return and to find the parameters and return values of message, if you want to don’t agree, will need to go to other processes. Three, message forwarding slow process Above said if you want to eventually return must find the news and the content of the parameters and return values are not consistent, need to go to other processes, so what is the process, we then have a look at just another way to methodSignatureForSelector official documentation:
Official methodSignatureForSelector returns a NSMethodSignature method signature is the definition of object, the object contains the by a given selector identifies the description of the method.
This method is used in the implementation of protocols. This method is also used in situations where an NSInvocation object must be created, such as during message forwarding. If your object maintains a delegate or is capable of handling messages that it does not directly implement, you should override this method to return an appropriate method signature. This method is used for protocol implementation. This method is also used when the NSInvocation object must be created for message forwarding. If your object maintains a delegate or can handle messages that it does not implement directly, override this method to return the appropriate method signature. We see at the end of the document that there is a method called forwardInvocation:
We go to the documentation for this method:
To respond to methods that your object does not itself recognize, you must override methodSignatureForSelector: in addition to forwardInvocation:. The mechanism for forwarding messages uses information obtained from methodSignatureForSelector: to create the NSInvocation object to be forwarded. Your overriding method must provide an appropriate method signature for the given selector, either by pre formulating one or by asking another object for one. “To the response object itself can’t identify method, in addition to forwardInvocation: outside, still must be rewritten methodSignatureForSelector:. Forwarding message mechanism using from methodSignatureForSelector: access to the information to create the NSInvocation object is forwarding. Your override method must provide an appropriate method signature for a given selector, either by specifying a formula beforehand or by asking another object to provide a method signature. Obviously, methodSignatureForSelector and forwardInvocation do not exist in isolation, need to appear together. Let’s go straight to the code:
-
(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@”%s — %@”,func,NSStringFromSelector(aSelector)); if (aSelector == @selector(saySomething)) { // v @ : return [NSMethodSignature signatureWithObjCTypes:”v@:”]; } return [super methodSignatureForSelector:aSelector]; }
-
(void)forwardInvocation:(NSInvocation *)anInvocation{ NSLog(@”%s “,func);
SEL aSelector = [anInvocation selector];
if ([[LGTeacher alloc] respondsToSelector:aSelector]) [anInvocation invokeWithTarget:[LGTeacher alloc]]; else [super forwardInvocation:anInvocation]; } then view the print:
As you can see, first came to the methodSignatureForSelector, then came to the forwardInvocation, saySomething message is looked up at last.
A few more points to note about the forwardInvocation:
The forwardInvocation method has two tasks: find an object that can respond to the message encoded in the inInvocation. This object need not be the same for all messages. The message is sent to this object using the anInvocation. The anInvocation will save the result and the run-time system will pick it up and pass it to the original sender.
The forwardInvocation method is implemented to do more than just forward messages. The forwardInvocation can also, for example, be used to combine code that responds to a variety of different messages, eliminating the hassle of having to write separate methods for each selector. The forwardInvocation method may also refer to several other objects in response to a given message, rather than just forwarding it to one object. NSObject’s forwardInvocation: only the dosNotRecognizeSelector: method is called, and it does not forward any messages. Therefore, if you choose not to implement forwardInvocation, sending an unrecognized message to the object will raise an exception. At this point, we have explored the slow process of message forwarding.
From dynamic message parsing to fast forwarding process to slow forwarding process, we can summarize the following flow chart:
We started with objc_msgSend and explored the process of sending a message, which is very helpful for understanding the underlying iOS. Of course, due to the author’s level, the process of exploration may have some flaws. Here’s a quick summary:
Dynamic method resolution is divided into object method dynamic resolution and class method dynamic method object method dynamic resolution requires message sender +(BOOL)resolveInstanceMethod:(SEL) SEL method class method dynamic resolution requires message sender + (BOOL) resolveClassMethod SEL (SEL) method
If dynamic method resolution fails, the message forwarding process is entered. Message forwarding is divided into two processes: Fast forward and slow forward fast forward implementation is forwardingTargetForSelector, Can let the other response to find the object of the message to do the work slowly forward implementation is the combination of methodSignatureForSelector and forwardInvocation, provides a more fine-grained control, to return to the method signature to the Runtime, The anInvocation is then used to send the message to the provided object, and the Runtime extracts the result and sends it to the original message sender.
Jq.qq.com/?_wv=1027&k… (iOS AC dress password: 123)