Runtime底层原理探究(三) --- 消息转发机制(动态方法解析)

968 阅读5分钟

当消息发没有从子类和父类查找到实现的时候,Runtime会给我们补救的机会。我们称为消息转发机制。这个机制分为动态方法解析转发

动态方法解析

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);//消息转发
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

源码里lookForwardImp里写着 如果没有实现该方法但是实现了resolve为true,且triedResolver为false则进入**_class_resolveMethod**,只会解析一次如果解析完成triedResolver就会变为true,进不来

/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

如果这个类不是元类则执行__class_resolveInstanceMethod,如果是元类的话则执行__class_resolveClassMethod,所以如果是类方法的话执行resolveClassMethod,那么会一直查找最后还会执行一次resolveinstancemethod,但是这个方法是执行的元类的resolveinstancemethod而不是类的,因为类是元类对象如果元类找不到就会往上层查找,元类的上层是根元类,根元类的父类指向NSObject,所以最后还会执行一次resolveInstanceMethod最终的实例方法。**,如果是实例方法则resolveInstanceMethod方法进行解析,那么我们在实际运行的时候会发现执行了两次解析方法,因为__class_resolveInstanceMethod又帮我们发送了一次消息

/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (__typeof__(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, 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 = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

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

然后可以在这个方法里面进行resolveInstanceMethod返回true的时候动态增加方法。进行处理,如果不在这里进行处理则消息进入转发流程。通过下面的isa走位图也可以清晰的查找出来。

消息转发流程

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

上面的动态方法解析如果没有拦截则会进入__objc_msgForward_impcache这个方法,这个方法是由汇编进行调用的,没有源码实现。源码实现是闭源的。大家都知道没有拦截会进行快速转发forwardTarget,然后快速转发会通过methodSginatureForSelector进行方法签名慢速转发.,我们要分析的肯定不能这么肤浅需要更深层次的来进行解析

extern void instrumentObjcMessageSends(Bool)


void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);

    objcMsgLogEnabled = enable;
}

这个函数可以进行打印信息比如打印一些底层log,这些信息最终会储存到一些地方地址是**./private/tmp** 目录,可以通过这些函数查看底层调用了那些信息那么怎么使用这个函数呢。 定义一个Person类 定义一个walk方法,注意之前一定要写上instrumentObjcMessageSends(YES)instrumentObjcMessageSends(NO)方法

这里就看到了消息转发的执行过程

initialize -> resolveClassMethod -> resolveInstanceMethod -> forwardingTargetForSelector -> methodSignatureForSelector -> doesNotRecognizeSelector

./private/tmp那么这个路径是怎么来的我们可以看到instrumentObjcMessageSends的源码里有一个objcMsgLogEnabled确认可以打印信息 然后执行objcMsgLogFD == -1 这是一个静态属性 然后logMessageSend会执行 当objcMsgLogFD = -1的时候会输出log当目录。

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 ());
        objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
        if (objcMsgLogFD < 0) {
            // no log file - disable logging
            objcMsgLogEnabled = false;
            objcMsgLogFD = -1;
            return true;
        }
    }

    // Make the log entry
    snprintf(buf, sizeof(buf), "%c %s %s %s\n",
            isClassMethod ? '+' : '-',
            objectsClass,
            implementingClass,
            sel_getName(selector));

    objcMsgLogLock.lock();
    write (objcMsgLogFD, buf, strlen(buf));
    objcMsgLogLock.unlock();

    // Tell caller to not cache the method
    return false;
}

那么我们看到log输入的流程当我们抛出异常的时候系统是如何来成功抛出来的呢,看到objc_msgForward_impcache的方法解析后执行了__objc_forward_handler

然后_objc_forward_handler在文件里面是优雅的将崩溃信息抛出来了

 // Default forward handler halts the process.
__attribute__((noreturn)) 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;

消息转发机制总结

OC的消息发送当接受这没有响应的时候系统给了两次反悔的机会,如果没有响应则会进入 消息转发流程 首先是动态消息解析通过resolveinstacnemessage,类方法是resolveclassmessage 进行处理如果返回true并通过runtime动态添加消息进行处理。如果未进行处理进入复杂的转发流程。这些流程是通过汇编进行的苹果并未开源,那么我们推断可以通过instrumentObjcMessageSends进行处理的到对应路径的一个文静从哪里可以看出响应的消息加载流程,异常的抛出是通过__objc_forward_handler,进行了对应的堆栈打印信息