Environment: Xcode 11.5

Source: objc4-781

A very important picture

Let’s go back to the previous objc_msgSend process:

  1. In assembly codeThe current classthecacheThrough a quick lookup, the core function isCacheLookup
  2. If you don’t find itThe current classSearch the list of methods in theThe parent classthecacheAnd method list for slow search, search up in turn.
  3. If the search process encounteredThe parent class = = nilorThe parent class cacheIn theimp=imp_forwardTo 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:

  1. Tests whether the current class and its parent class are implemented+resolveInstanceMethodClass methods.
  2. call+resolveInstanceMethodmethods
  3. 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:

  1. See if the current class is implemented+resolveClassMethodClass method
  2. call+resolveClassMethodMethods.
  3. Look in the metaclassselThe correspondingimpAnd 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. becauseZHYPersonThere 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+resolveClassMethodAdd 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: increaseNSObjectAre classified as follows: mainThe function is as follows:Only in theNSObjecttheresolveInstanceMethodMethod forZHYPersonClass instance methodinstanceMethodAnd class methodsclassMethodAfter 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,runtimeThe list of caches and methods will be searched again to confirmselWhether the corresponding parameter is addedimpIf still not foundimp, the message forwarding process will be performed. As shown below: forwardingTargetForSelectorIs 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 toolTake a look at some clues.

Disassemble to view pseudocode

  1. throughlldbInstructions to findCoreFoundationLocation:

In 2.finderFind the corresponding file in(3) intohopperTo search for the corresponding method__forwarding_prep_0__ 4. Click to skip to____forwarding___methodsIf that happensforwardingTargetForSelectorMethod, but returns empty orforwardingTargetForSelectorIf not implemented, it will be executedmethodSignatureForSelectorThe logic is as follows:5. If not implementedmethodSignatureForSelectorMethod, jump toloc_64dd7Location:“Was finally executeddoesNotRecognizeSelectorMethods.6. If it happensmethodSignatureForSelectorMethod, but returns a null value, jumps toloc_64e3clocationAnd it was finally executeddoesNotRecognizeSelectorMethods.

conclusion

So far, the flow chart of message forwarding is as follows:

aboutresolveInstanceMethodPerform the analysis twice

The figure above is the second executionresolveInstanceMethodWhen the stack information can be found to bemethodSignatureForSelectorMethod.CoreFoundationthe[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 calledNSObjectThe implementation of thesel.

NSObjectThe default implementation is to return the current passselIs signed by the method and is therefore calledclass_getInstanceMethodTo obtain the corresponding instance method, implementation is as follows:It is because thelookUpImpOrForwardIn theLOOKUP_RESOLVERThe argument will eventually trigger againresolveInstanceMethod.