OC底层原理探索之消息动态决议

232 阅读3分钟

initialize

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

顺着这个方法我们能发现系统在最开始的时候已经默认加载了当前类的initialize的方法,一共有3个方法会自动调用: 初始化/load/c++的构造函数

unrecognized selector底层原理

我们在程序运行的时候如果找不到一个方法,一般会报错unrecognized selector send to instance,那么到底发生了什么呢?我们回到这个消息转发lookUpImpOrForward这里

  if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
        imp = forward_imp;
        break;
    }

lookUpImpOrForward中发现找不到的话imp = forward_imp,这个方法里面

const IMP forward_imp = (IMP)_objc_msgForward_impcache;

所以我们的思路就到了_objc_msgForward_impcache,在源码中全局搜索该方法,command点击折叠箭头,由于我们之前分析的就是arm64的架构,所以就定位到了这里,没错又到了这里的汇编,我们发现这里就一行代码 image.png 继续找到__objc_msgForward image.png TailCallFunctionPointer是把x17放到x0 return所以这的主要代码是__objc_forward_handler,继续全局搜索发现定位到了c/c++这里的源码,又从汇编跳出来了

void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
__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);
}

objc_defaultForwardHandler里面的实现刚好就是我们上面的报错输出。

对象方法动态决议(resolveInstanceMethod)

lookUpImpForward方法里面跳出循环之后,没有找到imp的时候,走的是这里

	// No implementation found. Try method resolver once.
    // behavior = 3 & 2 = 2
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER; // behavior = 2 ^ 2 = 0  然后下一次就进不来了
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)

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

不是元类就到了resolveInstanceMethod(inst, sel, cls)这个方法里面

SEL resolve_sel = @selector(resolveInstanceMethod:);
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

系统自动发送一个resolveInstanceMethod:方法。我们可以在这个类里面实现消息的动态转发 ​

类方法动态决议(resolveClassMethod)

  if (! cls->isMetaClass()) {
     
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        // isa的走位图 元类里面的对象方法  类里面的类方法
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
   }

定位到resolveClassMethod(inst, sel, cls)方法,我们发现跟上面的对象方法一样系统会自动发送resolveInstanceMethod:方法

   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);

tips:这个nonmeta虽然是元类,但是这个方法并不是在元类里面实现,因为类的类方法在元类里面是以对象方法存在的。

消息转发流程引入

如果我们在上面的方法动态决议过程也没有实现,那么接下来的流程是怎么样的呢?这里介绍一个objc底层相关的日志打印externvoidinstrumentObjcMessageSends(BOOL flag)

// 慢速查找 
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person alloc];
        instrumentObjcMessageSends(YES);
        [Person say666];
        instrumentObjcMessageSends(NO);
        NSLog(@"Hello, World!");
    }
    return 0;
}

我们这里的Person类并没有实现say666的方法,我们在源码里面查找这个instrumentObjcMessageSends找到了信息打印相关的一个方法logMessageSend,在这里面找到了日志存储的地址

bool logMessageSend(bool isClassMethod,
                    const char *objectsClass,
                    const char *implementingClass,
                    SEL selector)
{
    char	buf[ 1024 ];
    // Create/open the log file
    if (objcMsgLogFD == (-1))
    {
        snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());
        // ...
    }
    // ...
    return false;
}

我们在源码里面找到了这个地址 "/tmp/msgSends-%d", command+ shift +G image.png 找到了一个msgSends-5914的文件 image.png

打开该文件 image.png 注意到Person的方法调用顺序,由上面就引出了我们耳熟能详的消息转发机制 ​

image.png