preface

In objc_msgSend for RunTime, we’ve looked at the quick lookup flow for messages, and if not found in the quick lookup flow, we get to __objc_msgSend_uncached, which is used for slow lookup. Below 👇 we will enter the slow search process on the line of analysis.

Source code analysis

Let’s go into the source code project and find__objc_msgSend_uncachedAssembly source, found to remove comments useful code on two lines.Let’s look at it firstTailCallFunctionPointer x17Method that simply jumps to return the value of x17.We don’t know exactly what’s in x17, so we have to look at the first method, rightMethodTableLookupWhat did I do? The literal meaningParty publishing search, let’s go into the source code.So x16 is class, so you have to remember that. In the source code, we find that_lookUpImpOrForwardThe return value of this method will be given to register X17, indicating that the return value of this method is most likelyimp. We’re looking for_lookUpImpOrForwardC/C ++ method, remove an underscore to continue to look.

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, Int behaviors) {/ * definition of forward behaviors = = 3 = LOOKUP_INITIALIZE | LOOKUP_RESOLVER * / const IMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; Class curClass; runtimeLock.assertUnlocked(); / * determine whether the class has been initialized, if there is no initialization behaviors = LOOKUP_NOCACHE | LOOKUP_INITIALIZE | LOOKUP_RESOLVER * / if (slowpath (! cls->isInitialized())) { behavior |= LOOKUP_NOCACHE; } runtimelock.lock (); CheckIsKnownClass (CLS); // Is the checkIsKnownClass already loaded? // Check whether the class is implemented. If not, it needs to be implemented first. Methods follow-up cycle of CLS = realizeAndInitializeIfNeeded_locked (inst, CLS, behaviors & LOOKUP_INITIALIZE); // runtimeLock may have been dropped but is now locked again runtimeLock.assertLocked(); curClass = cls; Reasonableclasscount ();) {/ / / / judge whether there is a Shared cache cache optimization the if (curClass - > cache. IsConstantOptimizedCache strict (/ * * / true)) {# if CONFIG_USE_PREOPT_CACHES / * */ imp = cache_getImp(curClass, sel); */ cache_getImp(curClass, sel); Done_unlock if (imp) goto done_unlock; curClass = curClass->cache.preoptFallbackClass(); #endif} else {// curClass method list. Meth = getMethodNoSuper_nolock(curClass, sel); If (meth) {imp = meth->imp(false); goto done; } // Until the parent of the current class is nil (that is, NSObject), assign the defined message forward to IMP, Slowpath ((curClass = curClass->getSuperclass()) == nil)) {// No implementation found, and method resolver didn't help. // Use forwarding. imp = forward_imp; break; }} // If there is a cycle in the superclass chain, If (slowpath(--attempts == 0)) {_objc_fatal("Memory corruption in class list "); Imp = cache_getImp(curClass, sel); // If (slowpath(imp == forward_imp)) {// Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } // Done if (fastPath (imp)) {// Found the method in a superclass. Cache it in this class.goto done; } } // No implementation found. Try method resolver once. /* behavior = 3 LOOKUP_RESOLVER = 2 3&2 = 0011 & 0010 = 0010 If (slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath(slowpath)))))))))))))))  LOOKUP_RESOLVER)) { behavior ^= LOOKUP_RESOLVER; // return resolveMethod_locked(inst, sel, CLS, behavior); } done: // 3 & 8 = 0011 & 1000 = 0000 = 0 if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) { #if CONFIG_USE_PREOPT_CACHES while (cls->cache.isConstantOptimizedCache(/* strict */true)) { cls = cls->cache.preoptFallbackClass(); } #endif log_and_fill_cache(CLS, imp, sel, inst, curClass); } done_unlock: runtimeLock.unlock(); if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { return nil; } return imp; }Copy the code

The source code has been marked with comments, let’s do a text summary.

Slow search process summary

  • Define message forwarding IMP
  • Determines whether the class has been initialized
  • Whether the class is loaded
  • Class has been realized, determine the parent class/metaclass link, convenient for recursive search
  • Recursive search
    • Determine whether shared cache optimization exists
      • If yes, go to the quick search process, after finding, insert cache to return to IMP
      • If not, perform a binary search for method in the current class’s methodList. If found, return the corresponding IMP. If not found, continue. Until you find the NSObject and break out of the loop.
    • If there is a loop in the parent chain, the throw stops
    • Quick lookup of IMP in parent cache
    • If the parent class returns forward_IMP stop looking and break out of the loop
    • If the parent class finds an IMP, sel and IMP are inserted into the cache
    • If neither the CLS nor the parent class is found, you will be given a chance to determine if the dynamic method resolution has been executed, or if it has not.

Detailed explanation of each method

checkIsKnownClass()

Attempt to use unknown class XXXX….

realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)

We found through the source code, the main implementation methods are implemented and initialized class methods, let’s take a look at these two methods respectively.

realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)

Through the source code we foundrealizeClassMaybeSwiftMaybeRelockandrealizeClassWithoutSwiftThese two methods, in fact, are recursive implementation inheritance chain and isa bitchain related classes

initializeAndLeaveLocked

Through the source code we found that initializeAndMaybeRelock and initializeNonMetaClass these two methods are actually recursive initialization inheritance chain and ISA bitchain related classes

Object method: first look at the current class, did not then look at the parent class, did not then look at the grandfather class ->… -> Finally look in the root class. Class method: first look at the metaclass of the current class, not the parent metaclass. -> do not go to the root metaclass again.

Binary search method

findMethodInSortedMethodList

Premise: It is very important that the list is already sorted, otherwise dichotomy cannot be used. An 🌰

If list count=8, we want sel to be in the second place.

The first cycle

  • count = 8, base = 0, probe = 0+4
  • 2! = 4
  • 2 < 4

The second cycle

  • count = 8>>1 = 4; base = 0,probe = 0+2
  • 2==2, find the method, return

If the count of the list =8, we find sel in the seventh place. The first cycle

  • count = 8, base = 0, probe = 0+4
  • 7! = 4
  • 7>4, base = 4+1 = 5, count = 8-1 = 7

The second cycle

  • count = 7>>1 = 3, base = 5, probe = 5+1 = 6
  • 7! = 6
  • 7>6, base = 6+1 = 7, count = 3-1 = 2

The third cycle

  • count = 2>>1 = 1, base = 7, probe = 7+0 =7
  • 7==7, find the method, return

Draw a rough flow chart. It’s time to show your talents.

cache_getImp

We found the source code and found that it was also implemented in assembly.Let’s see if this cache hook up here is the same as what we looked up quickly before.Through the source code, we found that,If there is a cache miss, nil is returned instead of a slow search. That's the big difference.

_objc_msgForward_impcache

We always throw the same error if the method is not implemented unrecognized selector sent to instanceWhy is that? Let’s analyze the source code of this method and come to a conclusion.The main finding here is that__objc_forward_handlerLet’s find the source code for this method.Everything is clear.

The full text summary

  • Instance methods (object methods) that start with a quick lookup in the current class.
    • If it cannot be found in the cache, a slow search is performed.
      • Slow find: method cache, return IMP.
      • Not found: parent class — root class –nil, parent class is only found in cache, return nil.
    • If found, return to IMP directly.
  • Class method, first in the metaclass to quickly find, its slow search parent chain is: metaclass — root metaclass — root class –nil
    • If it cannot be found in the cache, a slow search is performed.
      • Find: method to cache, return imp.
      • Not found: sequential metaclass — root metaclass — root class –nil.
    • If found, return to IMP directly.
  • If fast lookup, slow lookup can’t find a method implementation (IMP), tryDynamic method resolution.
  • If the dynamic method resolution still cannot be found, it proceedsforward.

The last two steps predict what will happen next time.

Slow search flow chart