preface
DoesNotRecognizeSelector or unrecognized selector sent to instance doesNotRecognizeSelector or unrecognized selector sent to instance doesNotRecognizeSelector or unrecognized selector sent to instance doesNotRecognizeSelector or unrecognized selector sent to instance doesNotRecognizeSelector or unrecognized selector sent to instance doesNotRecognizeSelector If the imp is not found in the method lookup process, forward_IMP is called
Forward_imp = _objc_msgForward_impcache, source code view under _objc_msgForward_impcache implementation, global search _objc_msgForward_impcache
- __objc_msgForward_impcache is the assembly implementation, the main code b __objc_msgForward
- __objc_msgForward TailCallFunctionPointer is a macro that jumps imp. The x17 register holds imp, and the __objc_forward_handler associated with X17 is named __objc_forward_handler
- Global search __objc_forward_handler no specific implementation in assembly, that is not in assembly, probably in C/C++ source code, global search objc_forward_handler, source code as follows
Error reported in _objc_FATAL familiar with the classic crash message error method is not implemented
In the fast and slow search process did not find IMP, do not directly crash, do not give a chance. No must give a chance, or I am not convinced, the system or stem but I ha, give a chance is dynamic method resolution
Dynamic method resolution
In lookUpImpOrForward, if an IMP is not found, the process resolveMethod_locked is dynamically resolved
ResolveMethod_locked dynamic method resolution is locked
- First determine whether CLS is a metaclass
- If it is not a metaclass but a normal class, then the instance method called jumps to the resolveInstanceMethod process
- If it is a metaclass, then the class method is called to jump to the resolveClassMethod process
- LookUpImpOrForwardTryCache quickly find and slow to find corresponding imp imp back and sel
ResolveInstanceMethod method
- First create SEL resolveInstanceMethod resolve_sel
- LookUpImpOrNilTryCache (CLS, resolve_sel, CLS ->ISA(true)) resolveInstanceMethod ISA class method. Cache the resolveInstanceMethod method
- Send a message directly to the class via MSG (CLS, resolve_sel, sel). ResolveInstanceMethod is a class method
LookUpImpOrNilTryCache (INST, SEL, CLS) Fast and slow lookup processes
- LookUpImpOrNilTryCache is used to determine whether sel imp is implemented in the resolveInstanceMethod method
- If it is implemented and not in the cache, lookUpImpOrForward is used to find sel and imp is inserted into the cache
- Imp = forward_IMP. The dynamic resolution is called only once. Done_unlock and done processes are used. Both SEL and forward_IMP are inserted into the cache for message forwarding
# # resolveClassMethod method
- The resolveClassMethod is already implemented in NSobject, as long as the metaclass is initialized to cache in the metaclass
- Call the resolveClassMethod method to implement the IMP corresponding to sel dynamically in the resolveClassMethod method
- Imp = lookUpImpOrNilTryCache(INST, SEL, CLS) Caches the IMP corresponding to sel, regardless of whether the IMP is dynamically added or not. If the IMP is not cached, it is forward_IMP
LookUpImpOrNilTryCache method
LookUpImpOrNilTryCache method name, which looks for IMP or nil as far as possible by querying the cache, LookUpImpOrNilTryCache is called in both the resolveInstanceMethod and resolveClassMethod methods
First of all, the last parameter is the default behaviors = 0, LOOKUP_NIL = 4, behaviors | LOOKUP_NIL greater than or equal to LOOKUP_NIL
To determine whether the CLS is initialized, it will be initialized
Find in cache
- Look for sel imp in the cache
- If imp exists jump done process
- Check whether there is a shared cache for the underlying system library
- If there is no IMP query in the cache, enter the slow search process
Slow search process
- In the slow lookup process, behavior= 4, 4&2 = 0 enters the dynamic method resolution, so it does not loop forever
- Most importantly, if imp= forward_IMP is not found, skip done_UNLOCK and done processes in lookUpImpOrForward, insert cache, and return forward_IMP
The done process
- The done process: (behavior & LOOKUP_NIL) and imp = _objc_msgForward_impcache. If forward_IMP is in cache, return nil. The LOOKUP_NIL condition is used to find whether AN IMP has been dynamically added or inserted into the cache
LookUpImpOrNilTryCache uses LOOKUP_NIL to control insertion into the cache, whether sel imp is implemented or not, and if IMP returns a value, it must be implemented dynamically in a dynamic method resolution
ResolveInstanceMethod instance exploration
The resolveInstanceMethod method was actually called before the crash
The resolution system automatically sends a message to the resolveInstanceMethod (resolveInstanceMethod).
tag
Dynamically add the sayTest2 method
Inside the main function, the sayTest method is called to print sayTest2 from the dynamically added sayTest2 result
- ResolveInstanceMethod invoked only once, because the dynamically add sayTest2 method directly obtained lookUpImpOrForwardTryCache imp, direct call imp, find the end of the process
- The crash also resolves the dynamic method resolution system to give it a chance
- Specific process: ResolveMethod_locked –> resolveInstanceMethod –> lookUpImpOrNilTryCache(inst, sel, CLS) — – > lookUpImpOrForwardTryCache – > call imp
ResolveClassMethod example exploration
Implement the resolveClassMethod method
Inside the main function, call sayHello and print sayKC with the dynamically added sayKC result
- The resolveClassMethod method was actually called before the crash, and it was called twice, with the same logic as when the resolveInstanceMethod method was called twice
- After resolveClassMethod is called, lookUpImpOrNilTryCache looks for sel imp. Sel IMP is forward_IMP in the metaclass cache. LookUpImpOrNilTryCache returns nil, which goes directly to resolveInstanceMethod because the class method is actually an instance method in the metaclass
- If finally no implementation lookUpImpOrForwardTryCache access to forward_imp into forward process
The resolveClassMethod is special
tag
Integrate dynamic method resolution
If no class method is dynamically added to the resolveClassMethod method, the resolveInstanceMethod in the metaclass is called. Write resolveInstanceMethod to a public class that calls both class methods and instance methods
- Instance method lookup flow: object –> class –> up to the root class (NSObject) –> nil
- Class method lookup flow: class –> metaclass –> up to the root class (NSObject) –> nil
You end up in the NSObject class, so this common class is the NSObject class
Instance methods are class method calls, and the system automatically calls the resolveInstanceMethod method, which is consistent with the above exploration. Dynamic methods determine the advantages
- You can handle method crashes in a unified manner. You can report method crashes to the server or jump to the home page
- If you have different modules in your project you can make business distinctions by naming them differently
- This approach is known as faceted programming and AOP
Differences between AOP and OOP
- OOP: In fact, it is the encapsulation of the properties and behaviors of objects. The same functions are extracted and encapsulated separately, with strong dependence and high coupling
- AOP: processing a certain step and stage, from which the extraction of the section, have repeated operation behavior, AOP can be extracted, the use of dynamic proxy, achieve the unified maintenance of the program function, dependence is small, the coupling degree is small, separate the AOP extraction of the function removed will not cause an impact on the main code. AOP is more like a three-dimensional vertical axis, in which the classes in the plane have a common logic connected by AOP, and the classes in the plane themselves have no relation to each other
forward
The fast and slow lookup process is not found, and the dynamic resolution method is not found, so the following will enter the message forwarding process, but the relevant source code is not found in objC4-818.2, the source code provided by CoreFunction is not detailed query. Apple still provides logging assistance
Log auxiliary
LookUpImpOrForward –> log_and_fill_cache –> logMessageSend, enter logMessageSend to see the source code