preface

In previous exploration, we have a basic understanding about methods in the oc of fast and slow search process, a new problem now is that because the runtime runtime mechanism, we declare function method, in the case of not implement, not complains, compiler can be passed, but at run time to collapse, So what exactly is the method doing at this point? Here’s the question to explore.

About _objc_msgForward_impcache

After entering the lookUpImpOrForward method, it is clear that the first line of code gives the IMP a default pointer of type _objc_msgForward_impcache. Let’s take a look at what type _objc_msgForward_impcache is. Click on the system source code and there is no corresponding implementation, then enter the assembly to view. Global search for __objc_msgForward

        STATIC_ENTRY __objc_msgForward_impcache
	b	__objc_msgForward
	END_ENTRY __objc_msgForward_impcache
Copy the code

You can see that it just jumps to __objc_msgForward, so search again

        ENTRY __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17
	
	END_ENTRY __objc_msgForward
Copy the code

After a global search for __objc_forward_handler, you can’t find it, so you can jump back to the source code based on the previous experience of jumping to lookupIMP.

Search globally for objc_forward_handler

void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;

#else

// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : The '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

Copy the code

In this case, recognized selector sent to instanceXXX if no method is found, including + and – methods, which means there is no class or object method. At the underlying level, there are object methods.

If this _objc_msgForward_impcache type goes through a series of processes and it’s not found or it’s reported that the method is not found, what’s going on?

resolveMethod_locked

With no method found in the class, superclass, or metaclass, we set a breakpoint before final processing

expand

So here’s an implementation of a singleton, behavior is 3, LOOKUP_RESOLVER is 2, two & is 2, and then 3^2 is 0, and then the next time you come in, 0^ anything is 0, so it’s not going to jump in again.

Click resolveMethod_locked to enter. The system does not crash directly because you gave IMP nil. Out of humanitarian spirit, the system still gives you a chance to turn over a new page.

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
Copy the code

System eventually again call lookUpImpOrForwardTryCache, click on take a look at this method

IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior);
}
Copy the code

Then click on the _lookUpImpTryCache method, and the system starts to determine whether it is initialized, and then cache lookup, share for cache? Slow search? And started again… But this re-walk isn’t for nothing, because we already had the if else, so then, it’s pretty clear, and we’re going to have to look at what’s going on in this if else? Take a closer look, take a closer look, the resolveInstanceMethod method is called, so that’s clearly the key.

ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();

    if(slowpath(! cls->isInitialized())) {// see comment in lookUpImpOrForward
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

    IMP imp = cache_getImp(cls, sel);
    if(imp ! = NULL) goto done; #if CONFIG_USE_PREOPT_CACHES
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}
Copy the code

resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if(! lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : The '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : The '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code

If you implement the resolveInstanceMethod method, you can still save it. If the class does not implement resolveInstanceMethod, then the current sel will be nil. The first if will not be returned, because the system will default to the implementation and return no.

Dynamic method resolution on class methods

In this process, we’re going to determine whether it’s a class method or an object method, and in object methods we’re going to go through resolveInstanceMethod, but in class methods we’re going to go through the following process.

if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
Copy the code

In this case, the class method is an object method of the metaclass, so you just need to write the class method in the current class to intercept it. The interesting thing about this is that while he’s looking for a class method, he’s also going through the resolveInstanceMethod, because the class method exists in the metaclass as an object method, so there must be a chain of inheritance in the metaclass, So resolveInstanceMethod is going to go down the metaclass inheritance chain until it finds nsobject, and see if it implements the resolveInstanceMethod method, so the interception method goes both ways.