preface
As we learned earlier, Apple provides a message dynamic implementation for fault tolerant management. If the dynamic implementation is not implemented, does it crash directly? The answer is no, Apple has given us another mechanism – message forwarding.
Message forwarding source
In the implementation of lookUpImpOrForward, there is code that deals with message forwarding
// 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
This section calls objc_msgForward_impcache, and then cache, let’s find the source code
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
ENTRY _objc_msgSend_noarg
b _objc_msgSend
END_ENTRY _objc_msgSend_noarg
ENTRY _objc_msgSend_debug
b _objc_msgSend
END_ENTRY _objc_msgSend_debug
ENTRY _objc_msgSendSuper2_debug
b _objc_msgSendSuper2
END_ENTRY _objc_msgSendSuper2_debug
ENTRY _method_invoke
// x1 is method triplet instead of SEL
add p16, p1, #METHOD_IMP
ldr p17, [x16]
ldr p1, [x1, #METHOD_NAME]
TailCallMethodListImp x17, x16
END_ENTRY _method_invoke
#endif
Copy the code
Find this is a piece of assembly source, not easy to understand, let’s see if we can see through the system log, there is a function in the analysis of the source code
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
Write a journal. Check it out
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 ? '+' : The '-',
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
ObjcMsgLogEnabled = objcMsgLogEnabled = objcMsgLogEnabled = objcMsgLogEnabled
void instrumentObjcMessageSends(BOOL flag)
{
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if(objcMsgLogFD ! = -1) fsync (objcMsgLogFD); objcMsgLogEnabled =enable;
}
Copy the code
Here we have this function that we can use to log
extern void instrumentObjcMessageSends(BOOL flag); int main(int argc, const char * argv[]) { @autoreleasepool { LGStudent *student = [LGStudent alloc] ; Dynamic method resolution / / object / / / / class methods - instrumentObjcMessageSends (true);
[student saySomething];
instrumentObjcMessageSends(false);
}
return 0;
}
Copy the code
After running the program, open the log file/TMP/msgfay-11857
+ LGStudent NSObject resolveInstanceMethod:
+ LGStudent NSObject resolveInstanceMethod:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent NSObject forwardingTargetForSelector:
- LGStudent LGStudent methodSignatureForSelector:
- LGStudent LGStudent methodSignatureForSelector:
+ NSString NSObject alloc
+ NSString NSString allocWithZone:
- NSPlaceholderString NSPlaceholderString initWithBytesNoCopy:length:encoding:freeWhenDone:
Copy the code
Found in the system and the two function called forwardingTargetForSelector, methodSignatureForSelector, so these two functions do you use?
Official Documentation
forwardingTargetForSelector
It can be seen that
- ForwardingTargetForSelector forward for the first time, that is, fast forward
- The return object of this method is the new object that executes SEL, that is, the object that cannot handle itself will forward the message to others for the processing of the relevant method, but cannot return self, otherwise it will never be found
- This method is more efficient, if not implemented or nl, the forwardInvocation: method is used for processing
- The underlying call objc_msgSend(forwardingTarget, sel…) ; To realize the sending of messages
- The receiver parameters and return values of the forwarded message must be the same as the original method
Code validation: let’s define a new class that implements the interface saySomething
@interface LGTeacher : NSObject
@end
@implementation LGTeacher
- (void)saySomething{
NSLog(@"%s",__func__);
}
@end
Copy the code
The message forwarding code is then implemented in the LGStudent class
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(saySomething)) {
return [LGTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
Copy the code
The final validation
LGStudent *student = [LGStudent alloc] ;
[student saySomething];
Copy the code
The output is:
The 2020-01-07 11:44:50. 947183 + 0800 008 - method to find the message forwarding (19647-237077) - [LGStudent forwardingTargetForSelector:] - saySomething [19647-237077] -[LGTeacher saySomething]Copy the code
methodSignatureForSelector
- This method generates an NSMethodSignature method signature based on the method selector SEL and returns it. This method signature encapsulates the return value type, parameter type, and other information.
- ForwardInvocation and methodSignatureForSelector must exist at the same time, the bottom will be through the method signature, generate an NSInvocation, passed as a parameter to invoke them
As you can see from the documentation
- 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.
Code validation
/ / / / slow forward - bottles unre - don't crash - (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 = [Invocation selector];if ([[LGTeacher alloc] respondsToSelector:aSelector])
[anInvocation invokeWithTarget:[LGTeacher alloc]];
else[super forwardInvocation:anInvocation]; }}Copy the code
The running results are as follows:
The 2020-01-07 13:38:08. 175563 + 0800 008 - method to find the message forwarding (31283-593368) - [LGStudent methodSignatureForSelector:] - saySomething 2020-01-07 13:38:08.177614+0800 008- Method Lookup - Message Forwarding [31283:593368] -[LGStudent forwardInvocation:] 2020-01-07 13:38:08.178789+0800 008- [LGTeacher saySomething]Copy the code
conclusion
So let’s sum it up with a picture