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

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

动态方法解析

// 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又帮咱们发送了一次消息ui

/***********************************************************************
* _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走位图也能够清晰的查找出来。spa

消息转发流程

// 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进行方法签名慢速转发.,咱们要分析的确定不能这么肤浅须要更深层次的来进行解析code

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)方法orm

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

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

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

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 get

而后_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,进行了对应的堆栈打印信息

相关文章
相关标签/搜索