No more BB, figure above.

In the previous chapter, we have seen that the object method is called objc_msgSend. Then we have analyzed the whole process of objc_msgSend. In CacheLookup, when the cache hit, the corresponding IMP implementation will be called directly. __objc_msgSend_uncached is executed for slow forwarding. How to use dichotomy to lookUpImpOrForward is analyzed. Everything is based on the search success, and there is no analysis did not find the method, how is handled?

@interface LGPerson : NSObject{ NSString *hobby; } @property (nonatomic, copy) NSString *name; @property (nonatomic) int age; SaySomething - (void)saySomething; + (void)sayNB; @endCopy the code

When the call calls [p saySomething], the return cannot be found and the program crashes and printsunrecognized selector sent to instance 0x101047030Why is this output?

Methods Dynamic resolution flow

The above diagram shows the code flow in the method objc_msgSend. When a recursive lookup method is used, if the corresponding IMP is not found, the method resolution is executed.

if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
Copy the code

In resolveMethod_locked, it checks whether the CLS is a metaclass. If not, execute resolveInstanceMethod.

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
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() ? '+' : '-', 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() ? '+' : '-', cls->nameForLogging(), sel_getName(sel)); }}}Copy the code

In resolveInstanceMethod, the CLS metaclass (CLS ->ISA metaclass) is determined first. IMP IMP = cache_getImp(CLS, SEL) If not, lookUpImpOrForward is called to recursively and slowly find out if resolveInstanceMethod is implemented. If a class implements resolveInstanceMethod and has been called before, it can be found in the cache next time), if the custom class is not implemented, NSObject is already implemented by default, so it must exist.

After determining whether the resolveInstanceMethod is implemented, the corresponding implementation is invoked directly through objc_msgSend.

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

It then looks for the [p saySomething] method again in the cache.

IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

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

Then pass to find resolveInstanceMethod, again to find [p saySomething], but this time the behaviors of | LOOKUP_NIL changed, if the cache IMP in direct return, If lookUpImpOrForward is not used, a recursive slow lookup of resolveInstanceMethod is implemented. Since the behavior changes at this time, the method resolution will not be executed again when no method is found at last.

if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
Copy the code

Instead, the IMP points to forward_IMP

const IMP forward_imp = (IMP)_objc_msgForward_impcache;

Insert log_and_fill_cache(CLS, IMP, SEL, INst, curClass) into the cache and return to resolveMethod_locked. Perform lookUpImpOrForwardTryCache (inst, sel, CLS and behaviors)

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

This method calls _lookUpImpTryCache, but at this point sel can be exchanged to IMP, Because log_and_fill_cache(CLS, IMP, SEL, INst, curClass) has inserted _objc_msgForward_impcache into the cache, Then directly recurse back to IMP (_objc_msgForward_impcache). If the method is not found, _objc_msgForward_impcache is returned, and _objc_msgForward_impcache is executed.

_objc_msgForward_impcacheWhat is?

Find the corresponding implementation in the assembly, internal directly to switch to__objc_msgForwardAnd then__objc_msgForwardLt.__objc_forward_handlerAssign to p17, then call x17 (TailCallFunctionPointer x17)

__objc_forward_handler

#if ! __OBJC2__ // Default forward handler (nil) goes to forward:: dispatch. 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)) ? '+' : '-', object_getClassName(self), sel_getName(sel), self); } void *_objc_forward_handler = (void*)objc_defaultForwardHandler;Copy the code

Finally, we found how to print the culprit of an unrecognized SELECT.

rewriteresolveInstanceMethodDynamic modification, ensure that the program continues to run.

- (void)sayNothing {
//    NSLog(@"%s",__func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s",__func__);
    IMP sayNothing = class_getMethodImplementation(self, @selector(sayNothing));
    Method method = class_getInstanceMethod(self, @selector(sayNothing));
    
    const char* type = method_getTypeEncoding(method);
    return class_addMethod(self, sel, sayNothing, type);
    
    return [super resolveInstanceMethod:sel];
}
Copy the code

By rewriting the resolveInstanceMethod method, dynamically add an IMP to the unimplemented SEL, so as to achieve the purpose of not crashing.

Question: Running the code will find that prints in resolveInstanceMethod print twice? (See follow-up analysis)