iOS底层探索-动态决议、消息转发

254 阅读6分钟

上篇文章 objc_msgSend 我们分析了底层从 cache 或 methodList 中获取方法的过程,我们知道了在 lookUpImpOrForward 方法中没找到方法后imp指针会被置为 forward_imp,这篇我们沿着这个思路查找 forward_imp 的内容,分析方法最后都没有被找到方法后的消息转发流程

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();
    ......
}

1、报错转发

if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
    // No implementation found, and method resolver didn't help.
    // 没有找到实现,方法解析器没有帮助
    // Use forwarding.
    imp = forward_imp;
    break;
}

1.1、_objc_msgForward_impcache

  • forward_imp 是通过 _objc_msgForward_impcache 得来,直接跳转的内容不是具体实现,所以我们全局搜索并找到arm64架构下的实现,又进入到了汇编代码 image.png
  • 其中实现的是 __objc_msgForward 方法,内容在紧邻的下边,比较重要的方法是 __objc_forward_handler

1.2、_objc_forward_handler

  • 全局搜索 _objc_forward_handler 的实现,可以看到熟悉的方法未找到的报错就是在这完成的,而且只是 以是否为元类来判断是类方法还是实例方法
    // 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);
    }
    

2、方法解析器

  • 在进行报错转发前,还有两点我们不能忘记
    1. 1.1 的图示英文注释写了:"没有找到实现,方法解析器没有帮助",从中我们可以得知,在进行报错转发前,系统尝试了对一个叫做方法解析器的东西进行求助,我们要分析一下其中进行了什么操作
    2. 虽然将imp指针置为了 forward_imp指针,但也仅仅是重置指针,实际还是需要调用的,在何处进行的调用也是需要分析一下的

2.1、执行一次方法解析器

  • lookUpImpOrForward 方法的for循环找方法、imp指针置为forward_imp结束后,还要执行下边的代码

    // No implementation found. Try method resolver once.
    // 没有实现.尝试一次方法解析器
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
    

    那么问题来了,既然是在for循环后才执行的,为什么在for循环中就提到了 "方法解析器没有帮助" 呢?往下分析就知道了

  • 方法解析器中首先对behaviorLOOKUP_RESOLVER操作的结果进行了判断 image.png

  • 进入判断后对behaviorLOOKUP_RESOLVER异或操作,如果后续未对未实现方法添加imp,内部方法中还会执行 lookUpImpOrForward,这次传入的 behavior 一定不会通过 与操作 的判断,保证了只执行一次方法解析器,然后就可以执行 forward_imp

2.2、resolveMethod_locked(包含最重要的动态决议)

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);
        //如果缓存中没有找到,那么最终还是走resolveInstanceMethod
        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);
}
  • 这里判断一下元类,是元类则调用resolveClassMethod,如果是类则调用resolveInstanceMethod
  • 如果缓存中没有找到,那么最终还是走 resolveInstanceMethod底层中不分类方法和实例方法,最终归宿都是实例方法resolveClassMethod 只是用来缓存方法,用来简化查找流程;而且按照正常思路,实例方法动态添加写到类中,那么类方法动态添加就应该写到元类中,但是元类是一个我们看不见摸不着的东西,如果不使用 resolveClassMethod 的话,查找动态添加的imp方法就需要往上一直找到NSObject才行,那样会大幅降低查找效率(resolveInstanceMethod 是真爱,resolveClassMethod 只是意外~
2.2.1、resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    //objc_msgSend 执行 resolveInstanceMethod:
    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 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));
        }
    }
}
  • 提供了第一次补救机会,如果在类中手动实现 + (BOOL)resolveInstanceMethod:(SEL)sel方法,为没有实现的方法动态添加imp指针,则可避免报错转发 image.png
2.2.2、resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() 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 resolveClassMethod:%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));
        }
    }
}
  • 可在类中实现+ (BOOL)resolveClassMethod:(SEL)sel为方法动态添加 imp指针,添加的方法可为实例方法,也可为类方法,但是要注意接收的对象是 类 还是 元类 image.png

    image.png

2.3、lookUpImpOrForwardTryCache --> _lookUpImpTryCache

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

因为前边提供了动态添加imp的补救机会,所以再在缓存里尝试找imp,如果没把握住机会,则会再走一遍 lookUpImpOrForward,这次就不会再触发方法解析器了

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)) {
    
        //这就是前边提到的还会走一遍 lookUpImpOrForward 的地方,因此进行与运算确保方法解析器只执行一次
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    //用户进行了补救操作,从缓存中拿到了imp指针
    return imp;
}

3、消息的快速转发

  • 如果未对方法动态添加imp指针,那么就会进入消息的快速转发流程,提供了第二次补救机会

  • -(id)forwardingTargetForSelector:(SEL)aSelector image.png

  • 可以专门写一个处理其他类未实现方法的类,对这些内容进行收集上报,帮助改善代码

4、消息的慢速转发

  • 快速转发未实现则进入慢速转发流程,提供了第三次补救机会
  • - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
  • - (void)forwardInvocation:(NSInvocation *)anInvocation image.png

总结

  1. lookUpImpOrForward 方法中未找到方法imp指针 --> 方法解析器
  2. resolveMethod_locked --> resolveInstanceMethod / resolveClassMethodresolveInstanceMethod 的近路) --> cache_getImp再找一遍cache,如果动态添加了imp就能在cache中找到
  3. 缓存方法(动态添加了imp)/ 再执行 lookUpImpOrForward未动态添加imp方法解析器的behavior值必为0
  4. 消息的快速转发:forwardingTargetForSelector,本类实现不了,找其他有同名方法的类去实现
  5. 消息的慢速转发:methodSignatureForSelectorforwardInvocation,更灵活的防止崩溃
  6. 报错转发:完蛋了

注: 可以在这些方法中交换imp指针,实现hook方法与埋点