preface

Last time we looked at __objc_msgSend_uncached

objc_msgSend_uncached

MissLabelDynamic = __objc_msgSend_uncached, MissLabelDynamic = __objc_msgSend_uncached, Search __objc_msgSend_uncached and find the entry. The real machine assembly code is as follows

A few simple lines of assembly code, the most important is method table Elookup and TailCallFunctionPointer x17, now I don’t know what X17 is, so look at what x17 does in TailCallFunctionPointer, The global search TailCallFunctionPointer code is as follows

TailCallFunctionPointer One line of assembly codebr $0. because0 = p17 .br $0 Read the meaning ofp17 The address in the register and jump to that address because we are querying the method according to findsel Looking for aimp In according to theTailCallFunctionPointer Prompt. guessp17 What the register should store isimp

Register p17 is not assigned to __objc_msgSend_uncached, and can only be assigned to methodTABLE ELookUP. The following code

through_lookUpImpOrForward Query toimp That will beimp Assigned tox17 Register, global search_lookUpImpOrForward Found no assembly in the_lookUpImpOrForward Definition or implementation of global searchlookUpImpOrForward

The return value of lookUpImpOrForward is IMP. Question: Why does lookUpImpOrForward use assembly?

Assembly is closer to machine language and queries are faster. The cache lookup process is finding methods quickly in the cache, whereas the slow lookup process is constantly iterating through the MethodList, so it’s slow and some of the parameters in a method are indeterminate, but in C they have to be deterministic, and assembly makes you feel more dynamic

conclusion

  • throughMethodTableLookup The query will findimp Exists as the return value in register X0, and assigns the value of register X0 to register X17
  • throughTailCallFunctionPointer x17 Direct callx17 Register inimp
  • __objc_msgSend_uncached–> MethodTableLookup –> _lookUpImpOrForward –> TailCallFunctionPointer

lookUpImpOrForward

LookUpImpOrForward imp query imp according to SEL

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    / / define the message forwarding forward_imp / / behaviors of the incoming is 3 = LOOKUP_INITIALIZE | LOOKUP_RESOLVER
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
    runtimeLock.assertUnlocked();
    / * / / whether class initialization Uninitialized behaviors = LOOKUP_NOCACHE | LOOKUP_INITIALIZE | LOOKUP_RESOLVER / / the first message sent to the class is usually + new + alloc or + self will initialize class * /

    if(slowpath(! cls->isInitialized())) { behavior |= LOOKUP_NOCACHE; }// Locking prevents multithreaded access from being corrupted
    runtimeLock.lock();
    checkIsKnownClass(cls);// Whether to register classes that are loaded by dyLD
    // Implement classes include the parent and metaclass that implement the ISA bit // initialize classes and parent classes
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;
    // Once the lock is acquired, the code looks for the class's cache again, but in most cases, the evidence suggests that most of the time it was missed, so time was wasted.
    // The only code path that does not perform some kind of cache lookup is class_getInstanceMethod().
    // unreasonableClassCount class iteration limit according to translation
    for (unsigned attempts = unreasonableClassCount();;) {
        // Check whether there is a shared cache cache optimization, usually the system method such as NSLog, the general method does not go
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {#if CONFIG_USE_PREOPT_CACHES
            /* Query the shared cache again, the purpose may be during your query other threads may call this method in the shared cache directly to query */
            imp = cache_getImp(curClass, sel);// Cache imp query according to sel
            // If imp exists, there is a jump to done_unlock
            if (imp) goto done_unlock;
            // Get an offset from the parent class
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            // Use a binary lookup algorithm to find methodList in curClass
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {  // If you find the sel corresponding method
                imp = meth->imp(false);// Get the corresponding IMP
                goto done;  // Jump to the done process
            }
            // curClass = curClass->getSuperclass() until if it is nil go if, if it is not nil go if
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                // if there is no sel method in the loop, assign forward_IMP to the imp
                imp = forward_imp;
                break; }}// Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {// Stop if there is a loop in the parent class
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        // Go to the parent cache to find imp
        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.
            // If the parent class returns forward_IMP to stop the search, the loop is broken
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            // Skip to done if there is one in the cachegoto done; }}// No implementation found. Try method resolver once.
    // If the query method is not implemented, the system attempts a method resolution
    // behavior = 3 LOOKUP_RESOLVER = 2
    // behavior & LOOKUP_RESOLVER = 3 & 2 = 2
    // Re-entering behavior = 1 & 2 = 0 will not execute the dynamic method resolution only once in the entry condition
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        //behavior = behavior ^ LOOKUP_RESOLVER = 3 ^ 2 = 1
        behavior ^= LOOKUP_RESOLVER;
        // Dynamic method resolution
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    //(behavior & LOOKUP_NOCACHE) == 0
    //LOOKUP_NOCACHE = 8 so (behavior & LOOKUP_NOCACHE) = 0
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        // Insert sel and IMP into cache note: insert sel and IMP into cache of current class
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    / / unlock
    runtimeLock.unlock();
    /* If (behavior & LOOKUP_NIL) is true, behavior! = LOOKUP_NIL and imp == forward_IMP is not queried directly return nil */
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}
Copy the code

Slow search process

  • Whether to register a class, if no error is reported
  • Whether to implementcls If there is no implementation, implement the class and relatedIsa a chainandIsa inheritance chainClass implementation, the purpose of method lookup to the parent class to query
  • Whether the class is initialized or not, and if it’s not initialized, this step I think is creating a class object like when you call a class method, that’s when the class object calls an instance method

The CLS starts traversing the query

  • Check if there is a shared cache, the purpose is that it is possible to call the cache during the lookup process, if there is a shared cache directly from the cache, start the query to this class
  • In a classBinary search algorithmLooks for methods in methodList, and if it’s inserted into the cache, the loop ends

Query in the parent cache

  • If a loop exists in the parent class, the query is terminated and the loop is broken
  • At this timecurClass = superclass If found, it is inserted into the cache of its own class. If the parent class returnsforward_imp Then, the traversal is displayed and message forwarding is performed
  • If not found in this classcurClass = superclass Enter andcls Class the same lookup process iterates through the loop untilcurClass = nil .imp = forward_imp Forwarding messages

Dynamic method resolution

  • ifcls The system will give you a chance to determine whether the dynamic method resolution has been executed. If not, go to the dynamic method resolution
  • If the dynamic resolution method is executed,imp = forward_imp Will godone The process inserts into the cache and goesdone_unlock processreturn imp The message forwarding process is displayed

Implement and instantiate classes

  • realizeClassMaybeSwiftAndLeaveLocked In the methodrealizeClassWithoutSwift Is to implement the classIsa a chainandInheritance chainRelated classes in
  • initializeAndMaybeRelock theinitializeNonMetaClass That initializes the class and its parent

Binary search algorithm

The methods in the method list are fixed, which means to followsel Sorted by size

  • The binary search algorithm is essentially finding the sum of the middle positions in the range every timekeyValue Compare, if equal, return the found method (of course, return the classification method if there is one)
  • If it’s not equal, we keep doing dichotomy queries, narrowing down the query, and if it’s still not found, we return nil

cache_getImp

Method quick search process is assembly source code implementation

The GetClassFromIsa_p16 macro definition is the same as when we started querying the cache method in this class, but with different parameters needs_auth = 0

Needs_auth ==0, p0=curClass. Assign the value of register P0 to register P16, P16 = curClass CacheLookup we’ve explored this method before and I’m not going to go into detail here CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant

If the cache does not hit the LGetImpMissDynamic process if the cache hit Mode = GETIMP

LGetImpMissDynamic process p0 = 0, which means no cache found or returns IMP = nil

P17 = imp, p17 = imp, x0 = p0 = imp, p17 = imp, x0 = p0 = IMP, p17 = imp, x0 = p0 = IMP, p17 = imp, x0 = p0 = IMP, p17 = imp, x0 = p0 = IMP

Examples demonstrate

1. The instance method tests creating a LGPerson class

Create a LGStudent class that inherits from LGPerson

Calling a method in main crashes

Unrecognized classic crash message, [student sayHello] also goes through the fast lookup process, the slow lookup process, the dynamic method resolution, finally the message forward, and finally not found the report unrecognized, DoesNotRecognizeSelector or unrecognized selector sent to instance, search in source code

Create an NSObject+LGCate class to add an object method sayEasy and implement it only in the class

mainCall a method inside a function

Class call object method call why does it work, there’s no instance method or class method at the bottom of oc, so getting a class method is actually getting an instance method of a metaclass, you don’t find it find the root class, you don’t have the root class, you find NSObject so you can find the sayEasy method, okay