Summary of basic principles of iOS
primers
In the previous two articles objc_msgSend Process analysis Fast Lookup and objc_msgSend process analysis Slow lookup, apple analyzed fast lookup and slow lookup of objc_msgSend respectively. In the case of failing to find a way to implement either method, Apple gave two suggestions
Dynamic method resolution
If the slow lookup process is not found, a dynamic method resolution is executedforward
: If the dynamic method resolution still does not find an implementation, the message is forwarded
If neither of these suggestions does anything, it will report an unimplemented crash of our common methods in daily development, as follows
Define LGPerson class where the sayMaster instance method and sayHappy class method are not implemented
Call LGPerson instance sayMaster and class sayHappy respectively in main, and run the program, both of which will give an error indicating that the method is not implemented, as shown below
- Error result from calling instance method sayMaster
- Error result from calling class method sayHappy
Method not implemented error source code
According to the source code of a slow search, we found that the error is finally to the __objc_msgForward_impcache method, the following is the source of the error process
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
Assembly implementation to find __objC_forward_handler, and did not find, in the source code to remove an underscore for global search _objC_forward_handler, there is the following implementation, the essence is to call the objc_defaultForwardHandler method
Looking atobjc_defaultForwardHandler
Does this look familiar? Here are the most common mistakes we make in daily development:Failed to implement the function, run the program, crash times error message
.
Now, let’s talk about how to prevent unimplemented crashes of methods before they crash.
Three method lookups to save the opportunity
Based on apple’s two suggestions, we have three salvage opportunities:
-
First chance dynamic method resolution
-
Message Forwarding Process
- 【 SECOND chance 】
Fast forward
- 【 Third Chance 】
Slowly forward
- 【 SECOND chance 】
First chance dynamic method resolution
In the slow search process did not find the method implementation, first will try a dynamic method resolution, its source code implementation is as follows:
It is mainly divided into the following steps
-
Determine whether a class is a metaclass
- If it is
class
, the implementation ofInstance methods
Dynamic method resolutionresolveInstanceMethod
- If it is
The metaclass
, the implementation ofClass method
Dynamic method resolutionresolveClassMethod
, if in a metaclassCould not find
Or forempty
While the,The metaclass
theInstance methods
Dynamic method resolutionresolveInstanceMethod
In the search, mainly becauseClass methods are instance methods in metaclasses
, so you also need to look for dynamic method resolutions for instance methods in the metaclass
- If it is
-
If the dynamic method resolution points its implementation to another method, the lookup continues for the specified IMP, that is, the lookUpImpOrForward process continues slowly
The process is as follows
Instance methods
ResolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod
It is mainly divided into the following steps:
-
Before sending a resolveInstanceMethod message, you need to look for an implementation of this method in the CLS class. LookUpImpOrNilTryCache is used to look up the resolveInstanceMethod in the lookUpImpOrForward slow lookup process
- If not, return directly
- If yes, send it
resolveInstanceMethod
The message
-
The lookUpImpOrNilTryCache method is used to look up the instance method in the lookUpImpOrForward slow lookup process
The resolveInstanceMethod dynamic resolution method prints “come” twice. You can see this by looking at the stack information
-
[first dynamic resolution] The first “coming” is the dynamic method resolution when searching for say666
-
Second second dynamic resolution 】 【 “come” is slowly forward process invokes the CoreFoundation framework of NSObject (NSObject) methodSignatureForSelector: after, will once again into the dynamic resolution
Note: Please refer to the problem exploration at the end of the article for detailed analysis process
Class method
For class methods, similar to instance methods, we can also solve the crash problem by overriding the resolveClassMethod class method in LGPerson and pointing the implementation of the sayNB class method to lgClassMethod
The CLS passed in is not a class, but a metaclass. You can get the metaclass of the class through the objc_getMetaClass method, because the class method is an instance method in the metaclass
To optimize the
Is there a better way to do this once and for all? In fact, it can be found that there are two search paths through the method slow search process
- Instance methods:
Class -- parent class -- root class -- nil
- Methods:
Metaclass -- root metaclass -- root class -- nil
What they all have in common is if they don’t find it, they all go to the root class which is NSObject, so can we integrate these two methods together? The answer is yes, you can do this by adding classes to NSObject, and because class methods look up, in their inheritance chain, also look up instance methods, you can put the unified handling of instance methods and class methods in the resolveInstanceMethod method, as shown below
+ (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
The implementation of this way, just with the source code for the method of class processing logic is consistent, that is, the perfect explanation why to call the method of class dynamic method resolution, but also call the object method dynamic method resolution, the fundamental reason is the method of class in the metaclass instance method.
Above this kind of writing, of course, there will be other problems, such as system method also can be changed, for this, can be optimized, namely we can according to the custom class method unified method name prefix, judging by the prefix is a custom method, and then unified handling custom methods, for example can pop before collapse to the home page, It is mainly used for anti-crash processing of APP online to improve user experience.
Message Forwarding Process
In the slow process to find out, we know that if the fast + slow didn’t find method, dynamic method resolution also not line, using the message forwarding, however, we haven’t found searched source message forwarding relevant source, can through the following ways to get to know, go which methods before collapse method calls
- through
instrumentObjcMessageSends
Mode Displays logs about sending messages - through
Hopper/IDA decompiling
Through instrumentObjcMessageSends
-
Through lookUpImpOrForward – > log_and_fill_cache – > logMessageSend, found at the bottom of the logMessageSend source instrumentObjcMessageSends source code to achieve, so, In the main call instrumentObjcMessageSends print method call log information, has the following two preparations
- 1. Open the
objcMsgLogEnabled
Switch, that is, callinstrumentObjcMessageSends
Method when passed inYES
- 2, in
main
Through theextern
The statementinstrumentObjcMessageSends
methods
- 1. Open the
extern void instrumentObjcMessageSends(BOOL flag); int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *person = [LGPerson alloc]; instrumentObjcMessageSends(YES); [person sayHello]; instrumentObjcMessageSends(NO); NSLog(@"Hello, World!" ); } return 0; }Copy the code
The logMessageSend source code shows that the sent messages are stored in the/TMP /msgSends directory, as shown below
Run the code and go to/tmp/msgSends
Catalog, found theremsgSends
At the beginning of the log file, opening it found that before crashing, the following methods were executed
-
Two dynamic method resolutions: the resolveInstanceMethod method
-
Two news fast forwarding: forwardingTargetForSelector method
-
Two news slowly forward: methodSignatureForSelector + resolveInvocation
Decompilation via Hopper /IDA
Hopper and IDA are tools that help us statically analyze visibility files, disassemble executable files into pseudocode, control flow diagrams, etc. Take Hopper as an example. (Note: Hopper Advanced is a paid software, and the demo is sufficient for simpler disassembly needs.)
- Run program crash, view stack information
Found ___forwarding___ from CoreFoundation
Through the Image List, read the entire image file, and then search for CoreFoundation to see the path to its executable
Follow the file path to the CoreFoundation executable
Open Hopper, select Try the Demo, and then drag the executable file from the previous step into Hopper to disassemble. Select x86(64 bits)
Search for __forwarding_prep_0___ through the search box on the left, then select the pseudocode
- The following is a
__forwarding_prep_0___
Assembler pseudocode, jump to___forwarding___
Here is ___forwarding___ pseudo code realization, first is to look at whether forwardingTargetForSelector method, if there is no response, jump to loc_6459b namely fast forwarding no response, forward into a slow process,
Jump to loc_6459b, below determine whether response methodSignatureForSelector method,
- if
There is no response
Jump toloc_6490b
, an error message is displayed - If you get
methodSignatureForSelector
theThe method signature
Nil, which is also an error
If methodSignatureForSelector the return value is not null, then in forwardInvocation approach to deal with invocation
Therefore, through the above two search methods can be verified, there are three methods of message forwarding
-
Fast forward forwardingTargetForSelector 】
-
[Slow forwarding]
methodSignatureForSelector
forwardInvocation
Therefore, in summary, the overall process of message forwarding is as follows
The processing of message forwarding is divided into two parts:
-
“Fast forward” when slow lookup, resolution and dynamic methods were not find implementation, for message forwarding, the first is a quick message forwarding, namely forwardingTargetForSelector method
- If the return
Message receiver
If it is still not found in the message receiver, it enters the search process of another method - If the return
nil
“, the slow message forwarding is displayed
- If the return
-
Perform to slow forward 】 【 methodSignatureForSelector method
- If the returned
The method signature
fornil
The directCollapse of the error
- If the returned method is signed
Don't is nil
And went toforwardInvocation
Method, the Invocation transaction is handled and no error is reported if it is not
- If the returned
Second chance: Quick retweet
According to the collapse of the above problem, if the dynamic method resolution but could not find, you need to rewrite forwardingTargetForSelector method in LGPerson, Specify the recipient of LGPerson’s instance method as LGStudent’s object (LGStudent class has a concrete implementation of sayInstanceMethod), as shown below
- (id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector)); Return [LGStudent alloc]; return [LGStudent alloc]; }Copy the code
The result is as follows
You can also call the method of the parent class without specifying the receiver. If no message is found, an error is reported
[Third chance] Slow forwarding
For a second chance in fast forward or not found, is in the final of a saving opportunity, namely in the LGPerson rewrite methodSignatureForSelector, as shown below
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s - %@",__func__,anInvocation);
}
Copy the code
The forwardInvocation method does not handle the invocation and does not crash
It is also possible to handle the Invocation transaction, as shown below, by modifying the Invocation target to [LGStudent alloc], The Invocation of LGPerson’s sayInstanceMethod will invoke LGStudent’s sayInstanceMethod
The print result is as follows
So, from the above, the program does not crash whether the forwardInvocation method handles the invocation transaction or not.
“Why are dynamic method resolutions executed twice?” To explore problems
The dynamic method resolution method mentioned in the previous article has been implemented twice, with the following two ways of analysis
Explore god’s perspective
In the slow lookup process, The resolveInstanceMethod method is executed via lookUpImpOrForward –> resolveMethod_locked –> ResolveInstanceMethod goes to the resolveInstanceMethod source code, which is triggered by sending the resolve_sel message, as shown below
IMP IMP = lookUpImpOrNilTryCache(INST, SEL, CLS); Add a breakpoint, print the stack through Bt and see what’s going on
- in
resolveInstanceMethod
In the methodIMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
Add a breakpoint and run the program until the first time"Come"
, using BTFirst dynamic method resolution
Is the stack information of selsayNB
- Continue until the second time
"Coming" print
, looking at the stack information, in the second, we can see that is throughCoreFoundation
the-[NSObject(NSObject) methodSignatureForSelector:]
Method, and then throughclass_getInstanceMethod
Going into dynamic method resolution again,
So with the stack information from the last step, we need to look at what’s going on in CoreFoundation? Through the Hopper of the disassembly CoreFoundation executable file, see methodSignatureForSelector method of pseudo code
By entering ___methodDescriptionForSelector methodSignatureForSelector pseudo code implementation
Enter ___methodDescriptionForSelector pseudo code realization, combine compiled stack print, you can see, In ___methodDescriptionForSelector this method calls the class_getInstanceMethod objc4-781
- Search the source code in objC
class_getInstanceMethod
, the source code implementation is shown below
This can be through code debugging to verify, as shown below, add a breakpoint at class_getInstanceMethod method, after execution methodSignatureForSelector method, returned to the signature, illustrate the method signature is effective, Apple gives the developer a chance to invocation again before the Invocation goes to class_getInstanceMethod, the method query say666 again, and then the dynamic method resolution again
Therefore, the above analysis also confirms the reason why the resolveInstanceMethod method was executed twice
Exploration without a God’s perspective
If there is no God perspective, we can also use the code to deduce where the dynamic method resolution is called again
- In the LGPerson rewrite
resolveInstanceMethod
Method, and plusclass_addMethod
Operating theAssignment IMP
At this time,resolveInstanceMethod
Will you go twice?