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

當(dāng)lookupImpOrForward函數(shù)從cache和methodTable中找不到對應(yīng)Method,繼續(xù)向下執(zhí)行就會來到resolveMethod_locked函數(shù)也就是我們常說的動態(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 的實現(xiàn)

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個方法稿存,我們來看一下實現(xiàn)晶姊。
cls->isMetaClass()的作用是判斷cls是否是元類朽基,并且對象的實例方法是存在類中的黑忱,而類方法是存在元類中的全庸,因此這里:

如果cls是類使兔,也就是實例方法會調(diào)用resolveInstanceMethod方法靶端,
如果cls是元類谎势,類方法則會調(diào)用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;
    }
    //調(diào)用類的resolveInstanceMethod方法
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //objc_msgSend(消息接收者杨名,方法名脏榆,參數(shù)),相當(dāng)于在類中調(diào)用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函數(shù)的入?yún)⒁来螢椋瑢嵗龑ο竽髡邸⒎椒韪㈩悓ο蟆?br> resolveInstanceMethod函數(shù)內(nèi)部會看到objc_msgSend函數(shù),可以看到消息的接受者是cls恨胚,所以說resolveInstanceMethod是一個類方法骂因。當(dāng)系統(tǒng)找不到方法,系統(tǒng)就會調(diào)用resolveInstanceMethod

我們在LGPerson類的.m內(nèi)部實現(xiàn)resolveInstanceMethod函數(shù)赃泡,并打印寒波,運行:

@implementation LGPerson

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

@end

從打印結(jié)果我們可以看到,系統(tǒng)在拋出異常之前會調(diào)用resolveInstanceMethod函數(shù)升熊,但是會調(diào)用2次俄烁,它為什么會調(diào)用2次呢,我們在文章的最后說级野。


image.png

我們知道objc_msgSend本質(zhì)就是通過方法名找具體實現(xiàn)的過程页屠。那我們在resolveInstanceMethod函數(shù)內(nèi)部去給該方法添加一個實現(xiàn)看看。我們首先實現(xiàn)一個method1方法蓖柔,然后獲取該方法的imp辰企,添加到本類里面。

image.png

這個樣運行時就不會拋出異常了况鸣,并且正常調(diào)用了method1牢贸。

當(dāng)我們動態(tài)的添加了resolveInstanceMethod()方法的時候,系統(tǒng)還是會調(diào)用lookUpImpOrNilTryCache()方法镐捧,去找IMP潜索。


image.png

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

下面我們來看一下類方法沒有實現(xiàn)時怎么處理玩焰。
剛才我們看到有一個resolveClassMethod由驹, 我們?nèi)フ{(diào)用一下類方法test2


image.png
image.png

打印確實看到調(diào)用了resolveClassMethod芍锚,并走到自己添加的實現(xiàn)昔园。


image.png

再次回到resolveMethod_locked方法中,我們看到元類在調(diào)用了resolveClassMethod之后并炮,如果元類中沒有imp默刚,那么又再一次調(diào)用了resolveInstanceMethod,這是為什么呢逃魄?


image.png

我們知道實例方法是存在類里邊荤西,而類方法是存放在元類中;那如果類方法找不到的時候,我們應(yīng)該調(diào)用元類的 resolveInstanceMethod邪锌。但是元類我們無法修改勉躺。根據(jù)類與元類的繼承關(guān)系,就會繼續(xù)往根元類找觅丰,最終找到NSObject的resolveInstanceMethod方法饵溅。這一整套調(diào)用鏈路會變得非常長,影響系統(tǒng)運行效率妇萄;(如果NSObject沒有resolveInstanceMethod方法蜕企,我們可以通過寫分類進行添加)。因此蘋果提供resolveClassMethod方法冠句,其實就是為了簡化類方法的查找流程轻掩,方便在類方法找不到時,直接通過resolveClassMethod來進行類方法決議懦底,提升調(diào)用效率唇牧;

image.png

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


image.png

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


image.png

消息轉(zhuǎn)發(fā)

如果系統(tǒng)在動態(tài)決議階段沒有找到實現(xiàn)烙肺,就會進入消息轉(zhuǎn)發(fā)階段纳猪。如果類中沒有實現(xiàn)resolveInstanceMethod方法,就會調(diào)用methodSignatureForSelector方法桃笙,我們就看下消息轉(zhuǎn)發(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判斷中有對打印控制的參數(shù),說明可以通過設(shè)置這個參數(shù)來進行打印日志星著;全局查找购笆,我們可以找到設(shè)置objcMsgLogEnabled的函數(shù),也就是通過instrumentObjcMessageSends函數(shù)來設(shè)置是否打印日志虚循。
/tmp/msgSends-%d通過查看logMessageSend的源碼如下圖可以看到同欠,日志輸出的路徑為/tmp/msgSends-XXX样傍,我們可以在此路徑下找到這個日志打印文件。


image.png

image.png

我們調(diào)用instrumentObjcMessageSends函數(shù)前铺遂,需要先聲明下該函數(shù)衫哥,如下:

extern void instrumentObjcMessageSends(BOOL flag);

然后打開該功能

instrumentObjcMessageSends(YES);

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


image.png

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

消息的快速轉(zhuǎn)發(fā)

我們在LGPerson類中聲明test3方法笛质,不實現(xiàn),然后定義一個LGBoy類, 在LGBoy類中實現(xiàn)test1捞蚂,然后在LGPerson類中實現(xiàn)forwardingTargetForSelector:方法妇押,將LGPerson的test3方法轉(zhuǎn)發(fā)到LGBoy類中,也就是說姓迅,快速轉(zhuǎn)發(fā)后的類必須有同名的方法敲霍。如下代碼:


image.png

image.png

轉(zhuǎn)發(fā)的作用在于,如果當(dāng)前對象無法響應(yīng)消息丁存,就將它轉(zhuǎn)發(fā)給能響應(yīng)的對象肩杈。并且方法緩存在接收轉(zhuǎn)發(fā)消息的對象的cache中

消息的慢速轉(zhuǎn)發(fā)

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


image.png

消息轉(zhuǎn)發(fā)流程總結(jié)

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末聋伦,一起剝皮案震驚了整個濱河市夫偶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌觉增,老刑警劉巖兵拢,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異逾礁,居然都是意外死亡说铃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門嘹履,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腻扇,“玉大人,你說我怎么就攤上這事植捎⊙媒猓” “怎么了阳柔?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵焰枢,是天一觀的道長。 經(jīng)常有香客問我,道長济锄,這世上最難降的妖魔是什么暑椰? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮荐绝,結(jié)果婚禮上一汽,老公的妹妹穿的比我還像新娘。我一直安慰自己低滩,他們只是感情好召夹,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著恕沫,像睡著了一般监憎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上婶溯,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天鲸阔,我揣著相機與錄音,去河邊找鬼迄委。 笑死褐筛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的叙身。 我是一名探鬼主播渔扎,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼信轿!你這毒婦竟也來了赞警?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤虏两,失蹤者是張志新(化名)和其女友劉穎愧旦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體定罢,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡笤虫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了祖凫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琼蚯。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惠况,靈堂內(nèi)的尸體忽然破棺而出遭庶,到底是詐尸還是另有隱情,我是刑警寧澤稠屠,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布峦睡,位于F島的核電站翎苫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏榨了。R本人自食惡果不足惜煎谍,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望龙屉。 院中可真熱鬧呐粘,春花似錦、人聲如沸转捕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽五芝。三九已至鳍咱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間与柑,已是汗流浹背谤辜。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留价捧,地道東北人丑念。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像结蟋,于是被迫代替她去往敵國和親脯倚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容