This chapter content

  1. What is the purpose of this chapter
  2. The source code of lookUpImpOrForward
  3. A slow lookup process for messages
  4. Dynamic protocols for messages, instance methods, and class methods

Purpose of this chapter

In the message assembly process, that is, in the message cache lookup process. If the message doesn’t match the cached method, it goes through the _objc_msgsend_cached process

The general process is: _objC_MSgSend_cached (assembly) -> lookUpImpOrForward (C/C++) -> TailCallFunctionPointer (assembly) -> Execution method

Purpose: The purpose of looking at this method is to clarify the slow process of finding messages if they are not found by the cache. This method also includes the dynamic protocol flow of the message (Apple gives us a chance to fix it in the normal lookup process). But what if the process is not found?

lookUpImpOrForward

The general process is:

1. Pre-initialization (if the class you are looking for has not been initialized)

2. Loop through all the methods of the class and its parent (if not found, go 3, if found, go 4)

3. If the method is not found, it will be given a chance to fix the dynamic protocol (if it is found, it will return IMP, if it is not found, it will return nil).

4. Insert into the message receiver’s own class cache (3 process does not go 4)

The source code

Post the source code, as far as possible remarks, can not read. Apple notes are too long, so delete them

IMP lookUpImpOrForward(id inst, SEL SEL, Class CLS, int behavior) {// Imp refers to the function that executes the built-in error. const IMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; Class curClass; runtimeLock.assertUnlocked(); // If (slowpath(! cls->isInitialized())) { behavior |= LOOKUP_NOCACHE; } runtimeLock.lock(); // See if CLS already knows the class. Prevents programmers from making a binary blob that looks like a class but isn't really a class. // A CFI attack has been made, hoping to register legally. CheckIsKnownClass (CLS); //1. This method contains all its parent classes and metaclasses. cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); // runtimeLock may have been dropped but is now locked again runtimeLock.assertLocked(); curClass = cls; //2. Loop to find the methods of its class, the list of methods of its parent class. Unsigned attempts = unreasonableClassCount();;) {if (curClass - > cache. IsConstantOptimizedCache strict (/ * * / true)) {/ / Shared cache, #if CONFIG_USE_PREOPT_CACHES IMP = cache_getImp(curClass, sel); 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; } // If the method is not found, check to see if there is a parent class. CurClss is the parent class of CLS. If (slowPath ((curClass = curClass->getSuperclass()) == nil)) {// No implementation found, and method resolver didn't help. // Use forwarding. imp = forward_imp; break; } // If the parent lookup process has a loop, after all, everything NSObject. It says stop with an error. 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; // Found the method in a superclass. Cache it in this class. } } // No implementation found. Try method resolver once. // 3. If the above loop is not found at the end of the message feed protocol. If (slowPath (behavior & LOOKUP_RESOLVER)) {behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); } // 4. Do the process, insert the method into the cache, if the shared cache is not important, do: 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

A slow lookup process for messages

We know more or less the process through the lookUpImpOrForward section. But there’s a method called getMethodNoSuper_nolock that you don’t know about, and that’s not really important. We know the process is good enough for most interviews. You can watch it if you’re interested

Source getMethodNoSuper_nolock

The source code is very simple, nothing more than a list of methods to traverse the class. And it’s very short, but it’s important to note that methods are a two-dimensional array. I don’t know why

static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); // fixme nil cls? // fixme nil sel? auto const methods = cls->data()->methods(); for (auto mlists = methods.beginLists(), end = methods.endLists(); mlists ! = end; ++mlists) { // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest // caller of search_method_list, inlining it turns // getMethodNoSuper_nolock into a frame-less function and eliminates // any store from this codepath. method_t *m = search_method_list_inline(*mlists, sel); if (m) return m; } return nil; }Copy the code

Source search_method_list_inline

That’s what the above method comes in and goes through. See if the list of methods obtained above is unordered or ordered. Ordered to binary search, disorderly words can only linear search, you can see his notes. Method acquisition is also a little special, it is divided into M1 and our normal system. M1 is in smallList.)

ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->isExpectedSize();
    
    if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
            return m;
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name() == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

    return nil;
}
Copy the code

Dynamic protocol for messages

What if we do not find the method in the above process? Apple has given a chance to fix this with the message dynamic protocol (see resolveMethod_locked in the lookUpImpOrForward source above). But how fast and how fast is the message forwarded? In fact, the forwarding speed of the message is after the dynamic protocol.

General process: Dynamic protocol not found -> message fast forwarding not found -> Message slow forwarding not found -> error

The two most important methods of message dynamic protocol are resolveInstanceMethod for instance object and resolveClassMethod for class method

Source resolveMethod_locked

Apple will use this method to determine whether the class of the recipient of the message is a class or a metaclass, because we know that object methods are stored in the class and class methods are stored in the metaclass. For details, see the class and Metaclass Nature article here. This article does not show the resolveInstanceMethod and resolveClassMethod, but you can check to see if the class implements them. If so, you can use objc_msgSend to set up the method

static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) { runtimeLock.assertLocked(); ASSERT(cls->isRealized()); runtimeLock.unlock(); // If CLS is not a metaclass, go to the instance dynamic protocol method if (! cls->isMetaClass()) { // try [cls resolveInstanceMethod:sel] resolveInstanceMethod(inst, sel, cls); } else {// resolveClassMethod(inst, sel, CLS); // Why is resolveInstanceMethod executed again? // Since the root metaclass inherits from NSObject (except NSProxy), it wastes performance by lying about class methods and instance methods. lookUpImpOrNilTryCache(inst, sel, cls)) { resolveInstanceMethod(inst, sel, cls); }} / / chances are that calling the resolver have populated the cache / / so attempt using it / / to try to find it again again, See if the cache is already handling the return lookUpImpOrForwardTryCache (inst, sel, CLS, behaviors); }Copy the code

Examples and Problems

Description: I create a class Person that implements no methods, create object p, and ask p to execute a method that does not exist, no doubt reporting an error. The instance dynamic protocol method is implemented in the Person class, and then printed in the dynamic protocol method.

Question 1: Why does resolveInstanceMethod walk twice?

1. The search is normal. 2 If the system does not find the message after forwarding, it calls back to search again.

(this answer is not detailed, the essence is the bottom in the first time dynamic protocol, forwarding is not found after the end of the callback process ___forwarding___ (CF library function) callback. Then there’s a function called _forwardStackInvocation between the libSystem library and CF that performs the operation invocation. In the specific process, you need to disassemble the CoreFoundation library first, and then find the final call result, you can return to the project breakpoint slow forwarding method to compile and view)

The first time is the normal lookup process, if the Person class implements the dynamic protocol method and then goes, and calls. What about the second time? At this point we need breakpoints and look at them using the method call stack, as shown below

Problem 2: Error message Received Unrecognized Selector sent to instance 0x100552570… How did it come about?

The lookUpImpOrForward method gives forward_IMP a default value of _objc_msgForward_impcache, which is actually an assembly function. The process is: _objc_msgForward_impcache -> __objc_msgForward -> _objc_forward_handler C/C++ function, Objc_defaultForwardHandler -> is an error. If someone asks, just say no method can be found and the bottom layer will give a default IMP error method pointing to

Question 3: Why should a class dynamic protocol be found in resolveInstanceMethod

Look at the source remarks

supplement

The dynamic protocol of the message, as well as the fast and slow forwarding of the message, have the advantage of non-invasive, no harm to the business code.

The dynamic protocol for messages has many fatal problems, such as logging the system in many ways (not something we need to fix) and code redundancy. So if you do AOP (faceted programming) you must not do message forwarding in this mechanism.