消息的動態(tài)決議

當lookupImpOrForward函數從cache和methodTable中找不到對應Method,繼續(xù)向下執(zhí)行就會來到resolveMethod_locked函數也就是我們常說的動態(tài)方法決議

    // No implementation found. Try method resolver once.
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

接下來看一下 resolveMethod_locked 的實現

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

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

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

里面有 resolveInstanceMethod 和 resolveClassMethod 2個方法,我們來看一下實現。
cls->isMetaClass()的作用是判斷cls是否是元類,并且對象的實例方法是存在類中的,而類方法是存在元類中的,因此這里:

如果cls是類,也就是實例方法會調用resolveInstanceMethod方法,
如果cls是元類,類方法則會調用resolveClassMethod方法,

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    //首先定義resolveInstanceMethod的方法
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    //先嘗試在類的緩存中查找是否有該resolveInstanceMethod方法
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        //沒有返回
        return;
    }
    //調用類的resolveInstanceMethod方法
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //objc_msgSend(消息接收者,方法名,參數),相當于在類中調用resolveInstanceMethod方法,返回true代表處理了該方法,否則就有問題。
    bool resolved = msg(cls, resolve_sel, 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 = lookUpImpOrNilTryCache(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        //處理錯誤信息
    }
}

resolveInstanceMethod函數的入參依次為,實例對象、方法名、類對象。
resolveInstanceMethod函數內部會看到objc_msgSend函數,可以看到消息的接受者是cls,所以說resolveInstanceMethod是一個類方法。當系統找不到方法,系統就會調用resolveInstanceMethod

我們在LGPerson類的.m內部實現resolveInstanceMethod函數,并打印,運行:

@implementation LGPerson

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s-----%@", __func__ , NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

@end

從打印結果我們可以看到,系統在拋出異常之前會調用resolveInstanceMethod函數,但是會調用2次,它為什么會調用2次呢,我們在文章的最后說。


image.png

我們知道objc_msgSend本質就是通過方法名找具體實現的過程。那我們在resolveInstanceMethod函數內部去給該方法添加一個實現看看。我們首先實現一個method1方法,然后獲取該方法的imp,添加到本類里面。

image.png

這個樣運行時就不會拋出異常了,并且正常調用了method1。

當我們動態(tài)的添加了resolveInstanceMethod()方法的時候,系統還是會調用lookUpImpOrNilTryCache()方法,去找IMP。


image.png

這個時候這個imp會不會被緩存呢,就是method1這個方法會不會被緩存,我們驗證一下。
通過獲取cache里的內容我們找到了test方法,也就是說當方法進行動態(tài)決議的時候是會被緩存的.

下面我們來看一下類方法沒有實現時怎么處理。
剛才我們看到有一個resolveClassMethod, 我們去調用一下類方法test2


image.png
image.png

打印確實看到調用了resolveClassMethod,并走到自己添加的實現。


image.png

再次回到resolveMethod_locked方法中,我們看到元類在調用了resolveClassMethod之后,如果元類中沒有imp,那么又再一次調用了resolveInstanceMethod,這是為什么呢?


image.png

我們知道實例方法是存在類里邊,而類方法是存放在元類中;那如果類方法找不到的時候,我們應該調用元類的 resolveInstanceMethod。但是元類我們無法修改。根據類與元類的繼承關系,就會繼續(xù)往根元類找,最終找到NSObject的resolveInstanceMethod方法。這一整套調用鏈路會變得非常長,影響系統運行效率;(如果NSObject沒有resolveInstanceMethod方法,我們可以通過寫分類進行添加)。因此蘋果提供resolveClassMethod方法,其實就是為了簡化類方法的查找流程,方便在類方法找不到時,直接通過resolveClassMethod來進行類方法決議,提升調用效率;

image.png

當類方法在動態(tài)決議時,如果沒有找到實現他還會調用resolveInstanceMethod()方法,所以還會調用method1()方法
下面這個例子,在元類中找方法的實現找不到,就要動態(tài)決議,會一直找, 變成死循環(huán)


image.png

在NSObject分類中寫上這個方法,不管是實例方法還是類方法找不到的崩潰就不會出現了,有利于程序的穩(wěn)定,這么寫類似aop(面向切面對象:在不修改源代碼的情況下,通過運行時來給程序添加統一的功能,用來進行埋點)


image.png

消息轉發(fā)

如果系統在動態(tài)決議階段沒有找到實現,就會進入消息轉發(fā)階段。如果類中沒有實現resolveInstanceMethod方法,就會調用methodSignatureForSelector方法,我們就看下消息轉發(fā)流程。

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    省略代碼...
 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
    省略代碼...
}
-------
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cls->cache.insert(sel, imp, receiver);
}

log_and_fill_cache方法中最主要的就是進行方法緩存cache.insert,但這里還有一個logMessageSend進行消息信息打印的過程,那么我們該如何獲取這個打印的消息呢?

objcMsgLogEnabled我們看到if判斷中有對打印控制的參數,說明可以通過設置這個參數來進行打印日志;全局查找,我們可以找到設置objcMsgLogEnabled的函數,也就是通過instrumentObjcMessageSends函數來設置是否打印日志。
/tmp/msgSends-%d通過查看logMessageSend的源碼如下圖可以看到,日志輸出的路徑為/tmp/msgSends-XXX,我們可以在此路徑下找到這個日志打印文件。


image.png

image.png

我們調用instrumentObjcMessageSends函數前,需要先聲明下該函數,如下:

extern void instrumentObjcMessageSends(BOOL flag);

然后打開該功能

instrumentObjcMessageSends(YES);

運行后,就可以在/tmp目錄下找到該文件:


image.png

可以看到在崩潰之前還調用了兩個方法forwardingTargetForSelector和methodSignatureForSelector方法。消息發(fā)送在經過動態(tài)方法解析仍然沒有查找到真正的方法實現,此時動態(tài)方法決議進入imp = forward_imp消息轉發(fā)流程。轉發(fā)流程分兩步快速轉發(fā)和慢速轉發(fā)。

消息的快速轉發(fā)

我們在LGPerson類中聲明test3方法,不實現,然后定義一個LGBoy類, 在LGBoy類中實現test1,然后在LGPerson類中實現forwardingTargetForSelector:方法,將LGPerson的test3方法轉發(fā)到LGBoy類中,也就是說,快速轉發(fā)后的類必須有同名的方法。如下代碼:


image.png

image.png

轉發(fā)的作用在于,如果當前對象無法響應消息,就將它轉發(fā)給能響應的對象。并且方法緩存在接收轉發(fā)消息的對象的cache中

消息的慢速轉發(fā)

在快速轉發(fā)過程中,如果我們不做處理,此時就會進入到methodSignatureForSelector方法, 也就是慢速轉發(fā)。


image.png

消息轉發(fā)流程總結

image.png
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容