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,進行了對應的堆棧打印信息

相關文章
相關標籤/搜索