Environment: Xcode 11.5
Source: objc4-781
A very important picture
Let’s go back to the previous objc_msgSend process:
- In assembly code
The current class
thecache
Through a quick lookup, the core function isCacheLookup
- If you don’t find it
The current class
Search the list of methods in theThe parent class
thecache
And method list for slow search, search up in turn. - If the search process encountered
The parent class = = nil
orThe parent class cache
In theimp=imp_forward
To execute the command onceMethod resolutionAnd returns the result.
Method resolution
The entry functions to the resolveMethod_locked method are: resolveMethod_locked
resolveInstanceMethod
ResolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); SEL resolve_sel = @selector(resolveInstanceMethod:); if (! lookUpImpOrNil(cls, resolve_sel, CLS ->ISA())) {resolveInstanceMethod is implemented; } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; // call the resolveInstanceMethod method to add dynamically added methods to the class's list of methods, if any. bool resolved = msg(cls, resolve_sel, sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. CLS // looks for SEL again in the current class. Note that beahvior is lookUpImpOrNil and LOOKUP_CACHE, so no dynamic resolution is performed to generate recursion if IMP is not found. If found, the IMP is cached in the current class. IMP imp = lookUpImpOrNil(inst, sel, cls); . } static inline IMP lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0) { return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL); }Copy the code
The resolveInstanceMethod provides the following functions:
- Tests whether the current class and its parent class are implemented
+resolveInstanceMethod
Class methods. - call
+resolveInstanceMethod
methods - Slow search again, if there is a dynamic add SEL corresponding IMP, it will be stored in the cache.
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls) { runtimeLock.assertUnlocked(); ASSERT(cls->isRealized()); ASSERT(cls->isMetaClass()); if (! lookUpImpOrNil(inst, @selector(resolveClassMethod:), CLS)) {resolveClassMethod class method Resolver not implemented. } Class nonmeta; // get the class object {mutex_locker_t lock(runtimeLock); nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst); // +initialize path should have realized nonmeta already if (! nonmeta->isRealized()) { _objc_fatal("nonmeta class %s (%p) unexpectedly not realized", nonmeta->nameForLogging(), nonmeta); } } BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; Bool resolved = MSG (nonmeta, @selector(resolveClassMethod:), sel); // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() IMP IMP = lookUpImpOrNil(inST, sel, CLS); 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
ResolveClassMethod and resolveInstanceMethod are similar:
- See if the current class is implemented
+resolveClassMethod
Class method - call
+resolveClassMethod
Methods. - Look in the metaclass
sel
The correspondingimp
And cache it.
resolveMethod_locked
static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); if (! CLS ->isMetaClass()) {resolveInstanceMethod, resolveInstanceMethod, Parse instance methods / / try/CLS resolveInstanceMethod: sel resolveInstanceMethod (inst, sel, CLS); } else {/ / try [nonMetaClass resolveClassMethod: sel] / / and/CLS resolveInstanceMethod: sel / / metaclass need to parse class methods resolveClassMethod(inst, sel, cls); // Find the corresponding method in the metaclass if (! LookUpImpOrNil (inst, sel, CLS) {// Since the class method is an instance method of the metaclass, the instance method needs to be parsed again. resolveInstanceMethod(inst, sel, cls); // chances are that calling the resolver have populated the cache // so attempt using it // So the search increased LOOKUP_CACHE, can in the current class cache based on assembly for a quickly find the return lookUpImpOrForward (inst, sel, CLS, behaviors | LOOKUP_CACHE); }Copy the code
ResolveMethod_locked determines the current class object. Non-metaclasses execute the resolveInstanceMethod methods, and metaclasses execute the resolveClassMethod and resolveInstanceMethod methods.
Summary of dynamic parsing
-
When we call an instance method/class method, runtime looks in the method cache and method list of the class/metaclass and its parent, and performs a dynamic resolution of the method if it cannot find one.
-
Dynamic resolution of methods allows developers to dynamically add methods to the current class/metaclass to solve the problem of having no SEL equivalent imp
-
Instance methods correspond to the +resolveInstanceMethod of the class.
-
Because class methods are equivalent to instance methods of metaclasses, dynamic resolution of class methods can correspond to the +resolveClassMethod method of a class and the +resolveInstanceMethod method of a metaclass.
-
Because classes and metaclasses are subclasses of NSObject, we can implement dynamic resolution of methods by overriding resolveClassMethod and resolveInstanceMethod in NSObject.
-
The metaclass root metaclass of the metaclass is also a subclass of NSObject, so we can implement dynamic resolution of instance and class methods simply by overriding the resolveInstanceMethod method in NSObject.
Code validation
Instance methods resolve dynamically
The above code is bound to crash. becauseZHYPerson
There is only the corresponding method declaration, but no implementation. Modified as follows:Normal printing is ok because the corresponding has been addedimp
.
Class method dynamic resolution
In the case of class methods, in+resolveClassMethod
Add a method to the metaclass.
The NSObject class add method dynamically resolves class and instance methods
Now let’s look at a slightly more advanced version: increaseNSObject
Are classified as follows: main
The function is as follows:Only in theNSObject
theresolveInstanceMethod
Method forZHYPerson
Class instance methodinstanceMethod
And class methodsclassMethod
After analysis, the program runs normally.
tips
An observant eye might notice that in the scene above, two are printed out, and the instanceMethod
, related content of message forwarding is involved here, which will be analyzed later.
forward
After dynamic parsing,runtime
The list of caches and methods will be searched again to confirmsel
Whether the corresponding parameter is addedimp
If still not foundimp
, the message forwarding process will be performed. As shown below: forwardingTargetForSelector
Is made up of__forwarding_prep_0__
Trigger. Check it out: __forwarding_prep_0__
Belong toCoreFoundation! [](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2d9b2d7a7a484e239a184d1fabc2d25e~tplv-k3u1fbpfcp-zoom-1.image)
Framework, but the framework is not open source, we can passHopper decompiler tool
Take a look at some clues.
Disassemble to view pseudocode
- through
lldb
Instructions to findCoreFoundation
Location:
In 2.finder
Find the corresponding file in(3) intohopper
To search for the corresponding method__forwarding_prep_0__
4. Click to skip to____forwarding___
methodsIf that happensforwardingTargetForSelector
Method, but returns empty orforwardingTargetForSelector
If not implemented, it will be executedmethodSignatureForSelector
The logic is as follows:5. If not implementedmethodSignatureForSelector
Method, jump toloc_64dd7
Location:“Was finally executeddoesNotRecognizeSelector
Methods.6. If it happensmethodSignatureForSelector
Method, but returns a null value, jumps toloc_64e3c
locationAnd it was finally executeddoesNotRecognizeSelector
Methods.
conclusion
So far, the flow chart of message forwarding is as follows:
aboutresolveInstanceMethod
Perform the analysis twice
The figure above is the second executionresolveInstanceMethod
When the stack information can be found to bemethodSignatureForSelector
Method. 在CoreFoundation
the[NSObject methodSignatureForSelector:]
You can see the entire implementation. It is important to note that these methods will normally override the custom flow that handles message forwarding if eventually calledNSObject
The implementation of thesel
.
NSObject
The default implementation is to return the current passsel
Is signed by the method and is therefore calledclass_getInstanceMethod
To obtain the corresponding instance method, implementation is as follows:It is because thelookUpImpOrForward
In theLOOKUP_RESOLVER
The argument will eventually trigger againresolveInstanceMethod
.