The source code

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead. **********************************************************************/ IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = nil; bool triedResolver = NO; runtimeLock.assertUnlocked(); // Optimistic cache lookup if (cache) {imp = cache_getImp(CLS, sel); if (imp) return imp; } // 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.read(); if (! cls->isRealized()) { // Drop the read-lock and acquire the write-lock. // realizeClass() checks isRealized() again to prevent // a race while the lock is down. runtimeLock.unlockRead(); runtimeLock.write(); realizeClass(cls); runtimeLock.unlockWrite(); runtimeLock.read(); } if (initialize && ! cls->isInitialized()) { runtimeLock.unlockRead(); _class_initialize (_class_getNonMetaClass(cls, inst)); runtimeLock.read(); // 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
    }

    
 retry:    
    runtimeLock.assertReading();

    // Try this classImp /// /// 1. Imp = cache_getImp(CLS, sel); if (imp) goto done; // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
    {
        unsigned attempts = unreasonableClassCount();
        for(Class curClass = cls->superclass; curClass ! = nil; curClass = curClass->superclass) { // Haltif there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if(imp ! = (IMP)_objc_msgForward_impcache) { // Found the methodin a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method // resolver for this class first. break; } } // Superclass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; } } } // No implementation found. Try method resolver once. if (resolver && ! triedResolver) { runtimeLock.unlockRead(); _class_resolveMethod(cls, sel, inst); runtimeLock.read(); // Don't cache the result; we don't hold the lock so it may have // changed already. Re-do the search from scratch instead. triedResolver = YES; goto retry; } // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}
Copy the code

lookUpImpOrForward

cache

Cache_ getImp; this cache_ getImp is a cache_getimp that is taken from the cache, not recursive, and does not execute the code that used the quick lookup (slow lookup never has a cache, Because I just did a quick lookup of the second argument passed in by calling this method is NO, the first argument means)

checkIsKnowClass

Check if the class is known and throw an exception if it is unknown. If it is a known class, it determines whether it has been implemented. If it has not been implemented, it assigns a value and then determines whether it has been initialized. If the class is not initialized, initialize it. If the message is Initialize, the class is initialized directly

Method Lookup process

At this point it also executes cache_ getimp (done here 1 to prevent concurrent resource grabs and 2 because the remap may already be held and no need to waste resources looking for it), or if it is not, a process is performed

  // Try this class's method lists. { Method meth = getMethodNoSuper_nolock(cls, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, cls); imp = meth->imp; goto done; }}Copy the code

Then pass a CLS and sel, find the corresponding IMP if it exists, cache directly to done, if not find the parent class to look for

 // Try superclass caches and method lists.
    {
        unsigned attempts = unreasonableClassCount();
        for(Class curClass = cls->superclass; curClass ! = nil; curClass = curClass->superclass) { // Haltif there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list."); } // Superclass cache. imp = cache_getImp(curClass, sel);if (imp) {
                if(imp ! = (IMP)_objc_msgForward_impcache) { // Found the methodin a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method // resolver for this class first. break; Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; }}}Copy the code

Find the parent class and loop around if the parent class is not nil, so keep looping, if the parent class method has a cache then it takes the cache and it caches it in the subclass method, if the parent class method doesn’t have a cache, it looks in the list of methods and it does cache assignment.

  // No implementation found. Try method resolver once.

    if(resolver && ! triedResolver) { runtimeLock.unlockRead(); _class_resolveMethod(cls, sel, inst); runtimeLock.read(); // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. // Use forwarding. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst);Copy the code

If no implementation is found, the dynamic method resolution process enters.

Ps: 04 Apr 2019 10:33:21 Add:

Cache_getImp does not look for IMPs from the cache. This method is not called recursively because cache_getImp is one

Slow search summary

If it’s not initialized, it initializes the class, first looks for the list of methods of the current class and finds the corresponding IMP by Selector, returns it and caches it if there is one, and then goes through the parent class until it’s nil and returns the parent method if it’s in the parent class. If the reslover parameter does not support message forwarding, the system enters the message forwarding process. If the reslover parameter does not support message forwarding, an exception is thrown.