类对象方法动态决议

687 阅读3分钟

方法慢速查找流程回顾

在上一篇文章分析了方法的慢速查找流程,但是遗留了一个点,如果我们没有查找到imp,后面会发生什么呢?当没有查找到imp image.png

方法找不到报错原理

首先先看源码

image.png image.png

当没有查到IMP的时候,返回的是objc_msgForward_impcache, 全局搜索

image.png 发现objc_msgForward_impcache 直接跳转到__objc_msgForward

image.png

image.png

TailCallFunctionPinter 就是调用当前的$0, 当前的$0x17, x17__objc_forward_handler, 全局搜索objc_forward_handler image.png 此时终于找到最终当我们没找到imp的报错方法, 相信大家应该都碰到过这类的错误. 在此补充说明下__attribute__

在此处__attribute__ ((noreturn, cold))

  1. noreturn该属性通知编译器函数从不返回值。当遇到函数需要返回值却还没运行到返回值处就已退出来的情况,该属性可以避免出现错误信息。C库函数中的abort()和exit()的声明格式就采用了这种格式
  2. cold 表示该函数比较冷门,这样在分支预测机制里就不会对该函数进行预取,或说是将它和其他同样冷门(cold)的函数放到一块,这样它就很可能不会被放到缓存中来,而让更热门的指令放到缓存中。 与之相反的有hot.

类对象方法动态决议

由汇编源码 image.png

可以知道behavior = LOOKUP_INITIALIZW | LOOKUP_RESOLVER, 因此这个if判断是通过的, 那此时behavior ^ = LOOKUP_RESOLVER, 调用resolveMethod_locked(inst, sel, cls, behavior); 接下来进入 static NEVER_INLINE IMP resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)

image.png

按照我们当前的流程cls->isMetaClass()返回false,所以后面就进入resolveInstanceMethod image.png 首先我们先去查找@selector(resolveInstanceMethod:),

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

此时behavior默认值为0, 调用_lookUpImpTryCache的时候,设置为LOOKUP_NIL, 附上_lookUpImpTryCache源码

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 (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
done:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}

此时输入的sel = @selector(resolveInstanceMethod:), 如果类实现了@selector(resolveInstanceMethod:)这个类方法(NSObject是实现了resolveInstanceMethod:方法), 调用resolveInstanceMethod:,因此此时是给我们一次机会去犯错,可以让我们在这个+ (BOOL)resolveInstanceMethod:(SEL)name方法中动态的添加imp, 类似如下代码:

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(unimplementMethod))
    {
        IMP setHobby = class_getMethodImplementation(self, @selector(setHobby:));
        Method method = class_getInstanceMethod(self, @selector(setHobby:));
        const char *type = method_getTypeEncoding(method);
        return class_addMethod(self, sel, setHobby, type);
    }
    return NO;
}

调用resolveInstanceMethod:结束之后,再查找一次imp

类方法动态决议

从上一小结我们可以知道,当clsmetaClass的时候,执行如下代码

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

看到这,可能会觉得很奇怪,为什么后面还要调用一次resolveInstanceMethod, 类方法是存储在元类的,那么其实类方法,就是元类的instanceMethod, 所以需要在此调用resolveInstanceMethod. 有了上述启发,当我们resolve类方法的时候,其实可以加一个NSObject的分类,重写resolveInstanceMethod:, 动态添加imp就一个,不需要也实现resolveClassMethod, 因为最终都会查找到NSObject这个根根元类里面来.

总结

image.png 本文只是分析了上图消息处理机制的其中一小部分,类方法和实例方法的动态决议. 关于后半部分forwarding请参见后续的文章