The first two objc_msgSend quick lookup and objc_msgSend slow lookup processes mainly analyze the fast lookup cache through the assembly process and slow lookup through the method list of the class. This chapter will focus on the in-depth analysis of the previous two chapters when the method is not found, Apple provides developers with two suggestions.
Dynamic method parsing
: Not found during a slow searchIMP
, will be executed onceDynamic method parsing
forward
If the dynamic method resolution is still not foundIMP
And began toforward
0x00 – forward_imp
If neither of the above steps is done, a crash is reported that is not implemented in the common error method of daily development
Example code:
@interface Student : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
- (void)sayNB;
- (void)sayMaster;
- (void)say666;
- (void)sayHello;
+ (void)sayNB;
+ (void)lgClassMethod;
@end
@implements Student
- (void)sayHello{
NSLog(@"%s",__func__);
}
- (void)sayNB{
NSLog(@"%s",__func__);
}
- (void)sayMaster{
NSLog(@"%s",__func__);
}
+ (void)lgClassMethod{
NSLog(@"%s",__func__);
}
@end
Copy the code
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [Student alloc];
[stu say666];
//[Student performSelector:@selector(sayNB)];
}
return 0;
}
Copy the code
Call instance method and class method in main method,
- Calling class methods
- Analysis:
Slow to find
In the source code ofIMP
If it is not found, it will assign a value calledforward_imp=(IMP)_objc_msgForward_impcache;
, by searching_objc_msgForward_impcache
, found in the corresponding schema assembly
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
Search __objc_forward_handler, removing an underscore according to the rules summarized earlier.
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : The '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
Copy the code
The actual essence is to call objc_defaultForwardHandler, which is the crash error we see all the time. Here’s an in-depth look at remedies before a crash occurs
0x01 – Dynamic resolution of the method
In the lookUpImpOrForward method, after the slow lookup of the method is completed, the method dynamic resolution process begins, giving the developer the first chance to deal with the failure to find the message.
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
Copy the code
Through comments can also be learned that this is the IMP did not find, will go here to solve, and only go once.
/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked(a);ASSERT(cls->isRealized());
runtimeLock.unlock(a);if (! cls->isMetaClass()) {// Check if it is a class method
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);// Call the parsing method of the instance
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls); }}// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
Copy the code
-
Mainly divided into the following steps:
-
Is CLS a metaclass?
- If it is
class
, the callObject methods
Dynamic analysis ofresolveInstanceMethod
- If it is
The metaclass
, the callClass method
Dynamic analysis ofresolveClassMethod
To process it and determine if it can be foundsel
If not found, call againresolveInstanceMethod
, because the class method, i.eMethod with a + sign
Relative to theThe metaclass
It’s also an instance method calledresolveInstanceMethod
The first parameter isInst = class
And the secondThe lookup is the sel method name
And the thirdCLS = metaclass
.
if (! lookUpImpOrNil(cls, resolve_sel, cls->ISA())) { // Resolver not implemented. return; }Copy the code
If the CLS ->ISA root metaclass is used to find the implementation of the resolution method, send a message if it cannot find the default implementation.
- If it is
-
Instance method crash fix
+ (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(say666)) {NSLog(@"%@ ", NSStringFromSelector(SEL)); // Get the sayMaster method imp imp imp = class_getMethodImplementation(self, @selector(sayMaster)); SayMethod = class_getInstanceMethod(self, @selector(sayMaster)); Const char *type = method_getTypeEncoding(sayMethod); // Get sayMaster method signature const char *type = method_getTypeEncoding(sayMethod); SayMaster return class_addMethod(self, sel, imp, type); } return [super resolveInstanceMethod:sel]; }Copy the code
Override the class method resolveInstanceMethod in the class. Before the message crashes, a dynamic resolution of the instance method is performed. In this method, the runtime points the missing SEL to an existing IMP and prints the result
You’ll see this method printed twice here, but I’ll leave it at the end of this article.
Class method crash fix
< span style = “max-length: 1em; clear: both; clear: both;
+ (BOOL)resolveClassMethod:(SEL) SEL {if (SEL == @selector(sayNB)) {NSLog(@"%@ ", NSStringFromSelector(SEL)); IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); const char *type = method_getTypeEncoding(lgClassMethod); return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type); } return [super resolveClassMethod:sel]; }Copy the code
โ ๏ธ To get a class method, add a class method to the metaclass. Use objc_getMetaClass to get the metaclass.
Summary and optimization
Through the dynamic analytical analysis of the above method, such a conclusion can be obtained
- Instance methods
Class -> parent -> root -> nil
ResolveClassMethod metaclass -> parent metaclass -> root metaclass -> root class -> nil
Class method (resolveInstanceMethod) Root metaclass -> root class -> nil
ResolveInstanceMethod or resolveClassMethod is overridden in the corresponding class. You can override resolveInstanceMethod in the root class NSObject to handle crashes of both instance methods and class methods.
ResolveInstanceMethod has a default implementation in NSObject
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
Copy the code
As follows, to create a classification of NSObject, unified handling as follows, because the default implementation, it returns NO, can’t call [super resolveInstanceMethod: sel] :
+ (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(say666)) {NSLog(@"%@ ", NSStringFromSelector(SEL)); IMP imp = class_getMethodImplementation(self, @selector(sayMaster)); Method sayMethod = class_getInstanceMethod(self, @selector(sayMaster)); const char *type = method_getTypeEncoding(sayMethod); return class_addMethod(self, sel, imp, type); }else if (sel == @selector(sayNB)) {NSLog(@"%@ ", NSStringFromSelector(sel)); IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); Method lgClassMethod = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(lgClassMethod)); const char *type = method_getTypeEncoding(lgClassMethod); return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type); } return NO; }Copy the code
, of course, this way of unified processing, there will be some problems, some systems will enter here, you can for unified method name prefix in class, according to the prefix judgment corresponding module to handle, such as mime module, belong to the collapse of the module and unified front page jump to mime module, report can also do some wrong operation.
0x03 – Message Forwarding Process
In the fast search + slow search is not found and dynamic message parsing is not processed, will enter the message forwarding process
At the end of lookUpImpOrForward, log_and_fill_cache has a control condition, objcMsgLogEnabled, that controls the log being saved locally and the flow of the call
Control the objcMsgLogEnabled is this function instrumentObjcMessageSends, give it to true, open log local preservation control
Find the following source code implementation via lookUpImpOrForward -> log_AND_fill_cache -> logMessageSend
bool objcMsgLogEnabled = false;
static int objcMsgLogFD = - 1;
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;
}
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
Because in this instrumentObjcMessageSends is internal functions, external use need to use the extern external declaration
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
instrumentObjcMessageSends(YES);
[person sayHello];
instrumentObjcMessageSends(NO);
}
return 0;
}
Copy the code
Through the above source code to understand the log save path in/tmp/msgSends
Directory, run the code, and you can see the following
At the beginning of open directory msgSends file, call the resolveInstanceMethod method, was not in dynamic analytic processing method, so come to forwardingTargetForSelector fast forwarding and subsequent slowly forward
0x04 – Hopper/IDA disassembler look at the flow
Hopper and IDA is a disassembly tool that can help us with static analysis. It disassembles executable files into pseudocode and flow charts to help us with analysis. Since IDA is unstable on MAC, it can be tested on Windows.
After running a crash, look at the stack information through BT,
Viewed in assembly, __forwarding___ is also in CoreFoundation.
throughimage list
Debugging Command ViewCoreFoundation image
The location of the
Once you find CoreFoundation, open it with Hopper
Open Hopper, Try the Demo, and drag in CoreFoundation
Click OK
By default, CLICK Next
Wait for the load to complete,
Search __forwarding_prep_0___ to see the pseudocode and jump to the pseudocode inside ___forwarding___
First of all determine whether forwardingTargetForSelector
- The jump to is not implemented
loc_64a67
- Can find implementation go
loc_649fc
Through theforwardingTargetForSelector
Gets the receiving object torax
And then torax
Error tolerant processing, error jumploc_64e3c
loc_64a67
Pseudo code
After the jump to here, first of all determine whether as zombies object, continue to determine whether methodSignatureForSelector response below,
- Does not respond to jump to
loc_64dd7
, directly report an error - The response then goes down, gets the return value, makes the fault tolerant processing, has the error to jump
loc_64e3c
loc_64dd7
Pseudo code andloc_64e3c
Pseudo code
By gettingmethodSignatureForSelector
Method signature isnil
Errors are also reported directly
The above process gets the method signature and begins processing in the forwardInvocation method
Therefore, through the above analysis, there are two types of message forwarding
- Fast forward
forwardingTargetForSelector
- Slowly forward
methodSignatureForSelector
+forwardingTargetForSelector
implementation
In lookUpImpOrForward, Slow doesn’t find IMP either,
- Step 1 start
Dynamic resolution of methods
Processing. If this step is not processed, goforward
forward
Step 1 startforwardingTargetForSelector
, i.e.,Fast message forwarding
, forwards the message to another object for processing. This step is not processedSlowly forward
Slowly forward
usemethodSignatureForSelector
Return method signature, cannot returnnil
orThe signature is empty
, using method signature generationNSInvocation
Object, so it needs to be overriddenforwardInvocation
Forwarding the message.
0x05 – resolveInstanceMethod
Why two executions?
To solve the problems left over before, in the instance dynamic method parsing, only rewrite, not to find sel processing, will call two times
Exploration of God’s perspective
LookUpImpOrForward -> resolveMethod_locked -> resolveInstanceMethod when an instance dynamic method is resolved
IMP IMP = lookUpImpOrNil(INST, SEL, CLS); Add a breakpoint, when sel is say666 stop, print say66 is coming and look at the stack through bt,
The first printed message, as can be seen from the stack, was printed the first time the method was dynamically resolved.
By printing it a second time, Through [NSObject (NSObject) methodSignatureForSelector:] – > __methodDescriptionForSelector – > Class_getInstanceMethod again came to the method of dynamic analysis and print the second, by analyzing the stack, can through the Hopper disassembling CoreFoundation file, check methodSignatureForSelector pseudo code
In jumped to ___methodDescriptionForSelector see its implementation
In conjunction with the previous stack letter view, objC’s class_getInstanceMethod method is called and viewed in the source code project
/*********************************************************************** * class_getInstanceMethod. Return the instance method for the * specified class and selector. **********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
if(! cls || ! sel)return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
Copy the code
Through the source view, again call lookUpImpOrForward here, and go a dynamic analytic method, system after call methodSignatureForSelector, return to the method signature, before the call invocation, Class_getInstanceMethod is called again, so lookUpImpOrForward is used again and SEL is queried. The dynamic parsing and message forwarding process are not found again.
No God perspective exploration
Because in the source code project exploration, so there is a God perspective, if there is no environment, how to verify the above process?
In a normal project, rewrite resolveInstanceMethod to resolve sel errors. Add an IMP using class_addMethod and see if the method passes twice.
+ (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(say666)) {NSLog(@"%@ ", NSStringFromSelector(SEL)); // Get sayMaster method imp imp imp = class_getMethodImplementation(self, @selector(sayHello)); SayMethod = class_getInstanceMethod(self, @selector(sayHello)); Const char *type = method_getTypeEncoding(sayMethod); // Get sayMaster method signature const char *type = method_getTypeEncoding(sayMethod); SayMaster return class_addMethod(self, sel, imp, type); } return [super resolveInstanceMethod:sel]; }Copy the code
By the result, by dynamic method resolution, IMP was assigned, only executed once, indicating that the second time is not here. According to the forward process, remove the imp in resolveInstanceMethod, rewrite forwardingTargetForSelector, and specify the [LGStudent alloc], to run, See if resolveInstanceMethod print two times, print two times, that before forwardingTargetForSelector carried out dynamic analytic method, on the other hand, is in after the execution of dynamic analytic method.
+ (BOOL)resolveInstanceMethod (SEL) SEL {if (SEL == @selector(say666)) {NSLog(@"%s -- %@ ",__func__, NSStringFromSelector(sel)); // // get sayMaster method imp // imp imp = class_getMethodImplementation(self, @selector(sayHello)); SayMethod = class_getInstanceMethod(self, @selector(sayHello)); // // Get sayMaster's method signature // const char *type = method_getTypeEncoding(sayMethod); // // redirect sel implementation to sayMaster // return class_addMethod(self, sel, imp, type); } return [super resolveInstanceMethod:sel]; } - (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector)); // runtime + aSelector + addMethod + imp return [LGStudent alloc]; }Copy the code
By running the results did not see, before promised to two times, shows that after forwardingTargetForSelector perform dynamic analytic method
According to the process, then rewrite methodSignatureForSelector and forwardInvocation
+ (BOOL)resolveInstanceMethod: SEL SEL {NSLog(@"%s -- %@ ",__func__, NSStringFromSelector(SEL)); If (sel == @selector(say666)) {// // get the imp of sayMaster method // imp imp = class_getMethodImplementation(self, @selector(sayHello)); SayMethod = class_getInstanceMethod(self, @selector(sayHello)); // // Get sayMaster's method signature // const char *type = method_getTypeEncoding(sayMethod); // // redirect sel implementation to sayMaster // return class_addMethod(self, sel, imp, type); } return [super resolveInstanceMethod:sel]; } - (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector)); return [super forwardingTargetForSelector:aSelector]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector)); NSLog(@"%p", [NSMethodSignature signatureWithObjCTypes:"v@:@"]); return [NSMethodSignature signatureWithObjCTypes:"v@"]; } - (void)forwardInvocation:(NSInvocation *)anInvocation{ NSLog(@"%s - %@",__func__,anInvocation); Target = [LGStudent alloc]; // Invocation invocation - Method [anInvocation invocation]; }Copy the code
Dynamic resolution through the above analysis, the second is between methodSignatureForSelector and forwardInvocation invocation, the second kind of analytical method validation results and the first kind of the disassembly result is the same. The result is the following graph
conclusion
This article is the last one of message flow analysis, method dynamic analysis and message forwarding
- First of all,
The message
throughThe assembly process
Quick lookup, no found jumplookupImpOrForward
Start slow search Slow search for messages
No luck either. GoMethod dynamic resolution
Method dynamic resolution
According to the news,Class method
orInstance methods
rewriteresolveInstanceMethod
andresolveClassMethod
Method, start the first remedyMethod dynamic resolution
It does not process the message and starts to forward the message, namely [fast forwarding].Fast forward
, that is, to rewrite theforwardingTargetForSelector
Method to dump the message to a processableobject
, for a second remedySlowly forward
usemethodSignatureForSelector
Return method signature, cannot returnnil
orThe signature is empty
, using method signature generationNSInvocation
Object, so it needs to be overriddenforwardInvocation
Forwarding the message.
Objective-c method signature and invocation
Principle and Practice of iOS Development ยท Runtime: Message Forwarding (Message mechanism, method not implemented +API incompatible crash, simulation of multiple inheritance)