Slow to find

Objc_msgSend is executed in source code when no method is found in the cache cacheCheckMissandJumpMiss, will perform a slow search.

CheckMiss is the method called when bucket->sel misses in the loop. JumpMiss is the method that is called when the first bucket is still found after the recursion ends.

Get the __objc_msgSend_uncached function for both CheckMiss and JumpMiss sources.

Implements __objc_msgSend_uncached:

Continue to find MethodTableLookup:

When _lookUpImpOrForward is queried, the search fails. Because from the bottom assembly to the upper C/C++ code call implementation, that is, a slow lookup process.

Fast lookup is a method lookup in the cache; Slow lookup is a lookup of methods from the class information data() and its parent class chain.


LookUpImpOrForward parsing

Look globally for lookUpImpOrForward:

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
  runtimeLock.assertUnlocked();   // Optimistic cache lookup  if (fastpath(behavior & LOOKUP_CACHE)) {  imp = cache_getImp(cls, sel);  if (imp) goto done_nolock;  }   // runtimeLock is held during isRealized and isInitialized checking  // to prevent races against concurrent realization.   // runtimeLock is held during method search to make  // method-lookup + cache-fill atomic with respect to method addition.  // Otherwise, a category could be added but ignored indefinitely because  // the cache was re-filled with the old value after the cache flush on  // behalf of the category.   runtimeLock.lock();   // We don't want people to be able to craft a binary blob that looks like  // a class but really isn't one and do a CFI attack.  //  // To make these harder we want to make sure this is a class that was  // either built into the binary or legitimately registered through  // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.  //  // TODO: this check is quite costly during process startup.  checkIsKnownClass(cls);   if(slowpath(! cls->isRealized())) { // If the current class is not implemented, first check the class and its parent class chain, method properties, etc  cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);  // runtimeLock may have been dropped but is now locked again  }   if(slowpath((behavior & LOOKUP_INITIALIZE) && ! cls->isInitialized())) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock);  // runtimeLock may have been dropped but is now locked again   // If sel == initialize, class_initialize will send +initialize and   // then the messenger will send +initialize again after this   // procedure finishes. Of course, if this is not being called   // from the messenger then it won't happen. 2778172  }   runtimeLock.assertLocked();  curClass = cls;   // The code used to lookpu the class's cache again right after  // we take the lock but for the vast majority of the cases  // evidence shows this is a miss most of the time, hence a time loss.  //  // The only codepath calling into this without having performed some  // kind of cache lookup is class_getInstanceMethod().   for (unsigned attempts = unreasonableClassCount();;) {  // curClass method list.  Method meth = getMethodNoSuper_nolock(curClass, sel);  if (meth) {  imp = meth->imp;  goto done;  }   if (slowpath((curClass = curClass->superclass) == nil)) {  // No implementation found, and method resolver didn't help.  // Use forwarding.  imp = forward_imp;  break;  }   // Halt if there is a cycle in the superclass chain.  if (slowpath(--attempts == 0)) {  _objc_fatal("Memory corruption in class list.");  }   // Superclass cache.  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;  }  if (fastpath(imp)) {  // Found the method in a superclass. Cache it in this class.  goto done;  }  }   // No implementation found. Try method resolver once.   if (slowpath(behavior & LOOKUP_RESOLVER)) {  behavior ^= LOOKUP_RESOLVER;  return resolveMethod_locked(inst, sel, cls, behavior);  }   done:  log_and_fill_cache(cls, imp, sel, inst, curClass);  runtimeLock.unlock();  done_nolock:  if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {  return nil;  }  return imp; } Copy the code


1.
if (fastpath(behavior & LOOKUP_CACHE)) {
    imp = cache_getImp(cls, sel);
    if (imp) goto done_nolock;
}
Copy the code

If lookUpImpOrForward is invoked in a multithreaded operation, imp can be fetched directly from the cache and goto done_nolock. After all, slow lookup is a time-consuming process, and Apple tries not to be too time-consuming.


2.
runtimeLock.lock();
Copy the code

Locking, as you can see from the comments, is a thread-safety issue for operation.


3.
checkIsKnownClass(cls);
Copy the code

Ensure that the incoming class is a legally registered class and has already been loaded.


4.
if(slowpath(! cls->isRealized())) { cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock); }Copy the code

If the current class is not implemented, if not, it implements and validates its parent class chain, method properties, and so on.

In the realizeClassWithoutSwift method, the parent chain of the current class is recognized

. supercls = realizeClassWithoutSwift(remapClass(cls->superclass),nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); . cls->superclass = supercls; cls->initClassIsa(metacls); .// Connect this class to its superclass's subclass lists
if (supercls) {
    addSubclass(supercls, cls);
} else {
    addRootClass(cls);
}
Copy the code

And it will be calledmethodizeClassTo associate data in the class information.

5.
if(slowpath((behavior & LOOKUP_INITIALIZE) && ! cls->isInitialized())) { cls = initializeAndLeaveLocked(cls, inst, runtimeLock); }Copy the code

There is a recursive function inside that determines whether the class and its superclass are implementedisInitializedIf not, class initialization is performed first.

The first few pieces of code above are preparation for slow lookups, and the for loop is the key part.


Slow find for loop

for (unsigned attempts = unreasonableClassCount();;) This for loop has no criteria and so on, it’s an infinite loop.

1.
Method meth = getMethodNoSuper_nolock(curClass, sel);
Copy the code

throughgetMethodNoSuper_nolock -> search_method_list_inline -> findMethodInSortedMethodList

2.
if (meth) {
    imp = meth->imp;
    gotodone; }... done: log_and_fill_cache(cls, imp, sel, inst, curClass); runtimeLock.unlock();Copy the code

Called when a binary lookup method is foundlog_and_fill_cache. log_and_fill_cacheWill be called tocache_fillThe cache fills while thecache_fillIs called tocache->insertMethods.

Method insertion into the cache is detailed in the class structure cache.


3
if (slowpath((curClass = curClass->superclass) == nil)) {
    // No implementation found, and method resolver didn't help.
    // Use forwarding.
    imp = forward_imp;
    break;
}
Copy the code

If the binary lookup does not find it, then curClass is assigned to its superclass and determines whether it is nil. If so, imp is assigned to forward_IMP and jumps out of the loop, otherwise it continues down.

Look upforward_imp

Global search_objc_msgForward_impcache, in assembly codeobjc-msg-arm64.sIn theFind the C++ codeobjc-runtime.mmIn theI see the usualunrecognized selector sent to instanceMethod not found.

So what this code means is that when no method is found according to the class and its parent up to the root class, the objc_msgSend message is forwarded to objc_defaultForwardHandler.


4.Key points of key points// Superclass cache.
imp = cache_getImp(curClass, sel);
Copy the code

By step 3, at this pointcurClassIs assigned its parent class, and its parent class is notnil.cache_getImpIt looks up from the parent’s cache. Cache, we also need to search for its implementation code in assembly.

Similar to quick lookup, this is also calledCacheLookup, but the parameter passed isGETIMPIt’s not a quick lookupNORMAL.CacheLoopupI’m not going to go over this, but when I can’t find it in the cache,CheckMissandJumpMissWill jump toLGetImpMissOnly one of them returned empty.

Assembly cache_getImp returns null if no method is found from the parent’s cache, the for loop continues, at which point the curClass becomes the parent of the original class, Method meth = getMethodNoSuper_nolock(curClass, sel) is called again; , the message is slowly searched from the class information of the parent class until it is found out of the loop, or the parent nil of the root class is found, and the message is forwarded to the unrecognized selector sent to instance.


Summary of flow chart: