OC底層探索11-objc_msgSend慢速查找流程

在上一篇文章中介紹了在匯編部分的緩存快速查找流程煤蹭。由于首次調(diào)用或者緩存擴(kuò)容等問題導(dǎo)致的緩存查找失敗钾军,就需要進(jìn)入慢速查找流程.

objc_msgSend慢速查找

慢速查找入口-匯編部分

  • 在快速查找流程無法找到對應(yīng)緩存的時候赎瞎,會跳到CheckMiss\JumpMiss這個macro中并且走到__objc_msgSend_uncached這個函數(shù)中。
    STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup   //跳轉(zhuǎn)
    TailCallFunctionPointer x17 //寄存器調(diào)用

    END_ENTRY __objc_msgSend_uncached
  • 繼續(xù)探索MethodTableLookup的實(shí)現(xiàn)宾茂,發(fā)現(xiàn)實(shí)現(xiàn)很長,但是都是一些寄存器的操作等,只看我們關(guān)心的b链烈、bl
.macro MethodTableLookup
...

// 省略影響跟流程的代碼
bl  _lookUpImpOrForward

...
  • 繼續(xù)搜索_lookUpImpOrForward發(fā)現(xiàn)找不到對用的實(shí)現(xiàn)了。蘋果編碼有一個習(xí)慣通過前綴增加挚躯、減少_,來實(shí)現(xiàn)多種語言的切換强衡。

通過調(diào)用lookUpImpOrForward,就來到了c++部分码荔。

通過調(diào)試來跟蹤流程

前文中的流程跳轉(zhuǎn)有一部分猜測的成分漩勤,現(xiàn)在通過調(diào)試來驗(yàn)證一下之前的猜測.

  1. 打開斷點(diǎn)感挥,找到目標(biāo)調(diào)用


  1. 打開堆棧信息選項(xiàng)


  1. 按住control 點(diǎn)擊 Step + info


  1. 點(diǎn)擊下一步來到__objc_msgSend_uncached
  1. 最終調(diào)用lookUpImpOrForward,而且給出了c++函數(shù)的位置

通過編譯調(diào)試進(jìn)一步驗(yàn)證了之前的猜測越败。

慢速查找c++流程

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    //參數(shù)準(zhǔn)備
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;
    
    //再進(jìn)行一次緩存的查找
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }
    
    runtimeLock.lock();    //加鎖
    
    // TODO: this check is quite costly during process startup.
    //判斷該類是否是已知類
    //這個操作需要從已知類表中做耗時的查找動作触幼;推測以后蘋果會優(yōu)化點(diǎn)
    checkIsKnownClass(cls);
    
    //判斷當(dāng)前類是否加載到內(nèi)存中(isa和rw信息是否存在)。
    if (slowpath(!cls->isRealized())) {
        //如果不存在進(jìn)行類的加載眉尸,并且會遞歸加載所有父類域蜗、元類。為了確定類的父類噪猾、isa鏈
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }
    
    //判斷類是否初始化霉祸,同樣是遞歸判斷所有
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        //對類進(jìn)行初始化,并且會遞歸加載所有父類袱蜡、元類丝蹭。為了確定類的父類、isa鏈
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }
    
    runtimeLock.assertLocked();    //解鎖
    
    //循環(huán)查找坪蚁,無跳出條件
    for (unsigned attempts = unreasonableClassCount();;) {
        //使用二分法在當(dāng)前類的方法列表methodList中查找該方法
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
        //本質(zhì)就是查找方法的imp實(shí)現(xiàn)
            imp = meth->imp;
            goto done;
        }

        //1. 將當(dāng)前類切換為父類
        //2. 如果當(dāng)前類的父類是nil奔穿,也就是在NSObject中都沒有找到則直接跳出循環(huán)
        if (slowpath((curClass = curClass->superclass) == nil)) {
            imp = forward_imp;
            break;
        }

        // 如果父類鏈中存在循環(huán),則停止
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        //對父類進(jìn)行方法查找
        //1.匯編緩存快速查找
        imp = cache_getImp(curClass, sel);
        //如果父類沒找到,首次會進(jìn)入動態(tài)方法解析
        if (slowpath(imp == forward_imp)) {
            break;
        }
        //在父類中找到了該方法
        if (fastpath(imp)) {
            goto done;
        }
    }
    
    //每個類首次都會進(jìn)行一次方法動態(tài)解析
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        //取反敏晤,保證該方法只走一次
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
    
 done:
     //存儲到緩存
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    //解鎖
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    //返回查詢結(jié)果imp
    return imp;

這就是整個消息imp查詢閉環(huán)流程(快贱田、慢)。

慢速查找流程圖

objc_msgSend慢速流程.png

cache_getImp沒有發(fā)生遞歸

STATIC_ENTRY _cache_getImp

GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp

.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
  1. 可以看到CacheLookup的第一個參數(shù)是GETIMP
  2. GETIMP下并沒有調(diào)用__objc_msgSend_uncached嘴脾,所以并不會產(chǎn)生遞歸男摧,只會查找當(dāng)前類的緩存

方法沒有找到,而且沒有實(shí)現(xiàn)方法解析

extern void _objc_msgForward_impcache(void);看到extern就知道它又是匯編代碼編寫的了

STATIC_ENTRY __objc_msgForward_impcache
//跳轉(zhuǎn)到 __objc_msgForward
b   __objc_msgForward
END_ENTRY __objc_msgForward_impcache


ENTRY __objc_msgForward
//定位
adrp    x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
//寄存器操作
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
  • 但是全局搜索__objc_forward_handler沒有找到下文了译打,猜測它是c++編寫耗拓,所以取一個_繼續(xù)搜索
  • 相信這段話已經(jīng)很熟悉了吧?平時開發(fā)應(yīng)該見過無數(shù)次了奏司。

MethodList(有序數(shù)組)二分查找

以上已經(jīng)解釋了慢速查找的整個流程乔询,現(xiàn)在對MethodList二分查找的實(shí)現(xiàn)做一個解釋。(核心方法findMethodInSortedMethodList)

ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(list);
    //當(dāng)前l(fā)ist是有序的韵洋,在類創(chuàng)建的時候就已經(jīng)進(jìn)行了加載
    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key; //key 等于 say666
    uint32_t count;
    //base相當(dāng)于low竿刁,count是max,probe是middle搪缨,這就是二分
    for (count = list->count; count != 0; count >>= 1) {
        //從首地址+下標(biāo) --> 移動到中間位置(count >> 1 右移1位即 count/2 = 4)
        probe = base + (count >> 1); 
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        //如果查找的key的keyvalue等于中間位置(probe)的probeValue食拜,則直接返回中間位置
        if (keyValue == probeValue) { 
            // -- while 平移 -- 找出分類重名方法
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                //找出分類重名方法
                //如果是兩個分類,就看誰先進(jìn)行加載
                probe--;
            }
            return (method_t *)probe;
        }
        
        //如果keyValue 大于 probeValue勉吻,就往probe即中間位置的右邊查找
        if (keyValue > probeValue) { 
            base = probe + 1;
            count--;
        }
    }
    return nil;
}

示例圖:

1. 向左查詢

8中找1

2. 向右查詢

8中找7
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末监婶,一起剝皮案震驚了整個濱河市旅赢,隨后出現(xiàn)的幾起案子齿桃,更是在濱河造成了極大的恐慌惑惶,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件短纵,死亡現(xiàn)場離奇詭異带污,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)香到,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門鱼冀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悠就,你說我怎么就攤上這事千绪。” “怎么了梗脾?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵荸型,是天一觀的道長。 經(jīng)常有香客問我炸茧,道長瑞妇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任梭冠,我火速辦了婚禮辕狰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘控漠。我一直安慰自己蔓倍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布润脸。 她就那樣靜靜地躺著柬脸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪毙驯。 梳的紋絲不亂的頭發(fā)上倒堕,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天,我揣著相機(jī)與錄音爆价,去河邊找鬼垦巴。 笑死,一個胖子當(dāng)著我的面吹牛铭段,可吹牛的內(nèi)容都是我干的骤宣。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼序愚,長吁一口氣:“原來是場噩夢啊……” “哼憔披!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤芬膝,失蹤者是張志新(化名)和其女友劉穎望门,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锰霜,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筹误,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了癣缅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撵摆。...
    茶點(diǎn)故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡腔呜,死狀恐怖瓢棒,靈堂內(nèi)的尸體忽然破棺而出镊掖,到底是詐尸還是另有隱情,我是刑警寧澤屡立,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布钾唬,位于F島的核電站,受9級特大地震影響侠驯,放射性物質(zhì)發(fā)生泄漏抡秆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一吟策、第九天 我趴在偏房一處隱蔽的房頂上張望儒士。 院中可真熱鬧,春花似錦檩坚、人聲如沸着撩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拖叙。三九已至,卻和暖如春赂乐,著一層夾襖步出監(jiān)牢的瞬間薯鳍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工挨措, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挖滤,地道東北人。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓浅役,卻偏偏與公主長得像斩松,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子觉既,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評論 2 361

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