9-慢速查找流程

前言

之前有講到過伞插,OC當(dāng)中幾乎所有的方法調(diào)用到底層都是消息的發(fā)送 objc_msgSend 掠廓,也探討了如何從cache 中通過 cmd 獲取 imp 這些流程羽历。但是當(dāng)我們從緩存當(dāng)找不到 imp 的時候又該怎么辦呢榜苫?
前面我們在class 的探討中提到過 bits 當(dāng)中存儲量很多的屬性铝宵、方法等谦纱。
前面我們在 objc_msgSend() 的查找過程中提到 objc_msgSend_uncached 這個方法看成,這個方法表示緩存查找不到,那么我們就從 objc_msgSend_uncached 這個方法入手跨嘉。

匯編緩存找不到

通過搜索 __objc_msgSend_uncached 我們可以找到 MethodTableLookup 才是關(guān)鍵

MethodTableLookup

于是我們?nèi)フ?MethodTableLookup 這個方法川慌,在這個方法里面我們最終找到了由 C++ 實現(xiàn)的 lookUpImpOrForward 方法。

.macro MethodTableLookup 
    SAVE_REGS MSGSEND 
    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16     //賦值祠乃,把x16( cls ) 賦值給 x2
    mov x3, #3      // x3 = 3
    bl  _lookUpImpOrForward

    // IMP in x0
    mov x17, x0
// 由于這里吧 imp 放在了x0里面梦重,所以x0 應(yīng)該就是上一個 跳轉(zhuǎn)的事件的返回值。所以我們的直接就去尋找 _lookUpImpOrForward亮瓷,
// 但是全局搜索 _lookUpImpOrForward 過程中發(fā)現(xiàn)沒有琴拧,于是乎我們就搜索 lookUpImpOrForward
// 發(fā)現(xiàn)在 objc-runtime-new 里面定義了 lookUpImpOrForward 這個方法。

    RESTORE_REGS MSGSEND
.endmacro

這里也說明了嘱支,我們 objc_msgSend 發(fā)送消息通過 cmd 獲取 imp 的過程是先用匯編在 cache 里面查找蚓胸,如果找不到了又會采用 C++ 繼續(xù)查找挣饥。

接下來我們?nèi)タ?lookUpImpOrForward 這個過程。
通過整個方法結(jié)果看到最后是一個 return imp; 的過程沛膳,所以我們的整個流程就是去查找 imp 什么時候賦值的扔枫。于是有了下面這段代碼

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
......
/// 這里是個死循環(huán) for (<#initialization#>; <#condition#>; <#increment#>)  通過這個定義可以知道, condition 沒有锹安,也就是沒有終止條件短荐。
    for (unsigned attempts = unreasonableClassCount();;) {
        
        /// isConstantOptimizedCache 這個方法從其具體的定義和實現(xiàn)的判斷(objc_cache.mm 里面)只有在真機的環(huán)境下才會有可能存在。
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            /// 這里也印證了只有真機的環(huán)境才需要這樣做叹哭。有可能在查找的過程中有了緩存忍宋,那么直接返回。
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            /// 這里才是真正的去獲取 imp的流程
            
            if (meth) {
                imp = meth->imp(false);
                goto done;      /// 獲取到了imp 直接 goto done
            }

            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        /// 如果還是獲取不到那么就去從父類厘米獲取风罩,然后又走cache 那套流程這樣形成一個遞歸像上的流程糠排。
        /// 如果一旦獲取到了,那么應(yīng)該走上面的 goto done泊交, 下面的 goto done 判斷應(yīng)該只是一個放置意外吧乳讥。
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }
......
    return imp;
}

通過上面的分析柱查,我們最終找打了 getMethodNoSuper_nolock -> search_method_list_inline 這個方法廓俭, 最后找到了 findMethodInSortedMethodList 這個方法。findMethodInSortedMethodList 這個方法就是我們的查找流程唉工,由于是排好序的研乒,所以按照最優(yōu)解就是二分查找法。于是就到了我們二分查找法去查找 imp 的過程了淋硝。

慢速查找流程

是應(yīng)該去找父類還是去找方法列表雹熬。

在整個查找的主線中有點小問題

二分查找

上面最終找到了 findMethodInSortedMethodList 這個方法,那么我們?nèi)タ纯?findMethodInSortedMethodList 的實現(xiàn)谣膳。

template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);

    auto first = list->begin(); /// 獲取第一個
    auto base = first;
    decltype(first) probe;  /// 這個是我們想要返回的結(jié)果竿报。

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    /**
     假如我們一共有 8 個方法,即 count = 8 , 那么 probe 就應(yīng)該是從 0 ~7 當(dāng)中继谚,假定結(jié)果為7烈菌,key = 7,最后一個
     即:base = 0花履, probe = 0芽世,key = 7, count = 8
     
     那么第一次for 循環(huán)相當(dāng)于
     for (count = 8, count !=0, count = 4)    count >>= 1  ->   count = count >> 1 ->  1000 >> 1 = 0100 = 4
     count = 8,   base = 0;
     probe = 0 + 4 = 4;
     4 != 7  所以查找失敗
     由于 7 > 4 ,所以 base = 5 诡壁, count = 3
     
     進入第二次循環(huán)
     coun = 3, base = 5
     probe = base + count >> 1 = 5 +  1 = 6;
        由于 6 != 7
     所以沒查找失敗
     由于 7 > 6  , 所以 count = 2-1 = 1, base = probe + 1 = 6 + 1 = 7
     
     第三次循環(huán)
     count = 1, base = 7
     probe = base + count >> 1 = 7+0 = 7
     由于7 == 7 济瓢,所以查找成功
     
     */
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        /// 獲取probe 的name ,其實就是 SEL
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                /// 這里就是 category 的同名方法是怎么存的就怎么取妹卿。也就是最后編譯的同名方法在前面旺矾,這個就是循環(huán)取最前面的一個蔑鹦。
                probe--;
            }
            /// 從結(jié)果看,這里是有這個才是有用的返回箕宙,那么結(jié)果必然會到這里举反。
            return &*probe;
        }
        
        //如果沒有找到, 并且value 大于當(dāng)前查找的 key扒吁,說明在后面火鼻,那么下次的循環(huán)就變成
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
} 

同理,比如我們要找小端位置的結(jié)果呢雕崩?如下所示魁索。

如果我們查找小的呢,比如0號位置盼铁。
     base = 0粗蔚, probe = 0,key = 0饶火, list.count = 8
     那么第一次為
     for (8, 8 != 0, 8 >>= 1)
     probe = base + count >> 1 = 0 + 4
     因為 4 != 0 鹏控,所以查找失敗
     由于 0 > 4 不成立,所以 base 和 count 不變
     
     第二次循環(huán)
     count = 4, base = 0
     probr = base + count >> 1 = 0 + 2 = 2
     因為 2 != 0 所以查找失敗
     由于 0 > 2 不成立肤寝, 所以 base 和 count 不變
     
     第三次
     count  = 2, base = 0
     probr = base + count >> 1 = 0 + 1 = 1
     因為 1 != 0 所以查找失敗
     由于 0 > 1 不成立当辐, 所以 base 和 count 不變
     
     第四次
     count = 1, base = 0
     probr = base + count >> 1 = 0 + 0 = 0
     因為 0 == 0 ,所以查找成功鲤看,返回
     

通過上面的結(jié)果缘揪,我們找到了有可能找到了imp, 但是如果沒有找到呢义桂?
看看幾個判斷.

  • 1找筝、判斷 getSuperclass 是否為空,如果為空慷吊,那么就執(zhí)行 forward_imp袖裕。
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
  • 2、imp = cache_getImp(curClass, sel); 遞歸像父類查找

到此處溉瓶,如果imp 存在急鳄,那么我們一定可以找到,然后執(zhí)行 goto done;

 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);
    }

log_and_fill_cache 這個最后又會執(zhí)行 cache.insert

總結(jié):

這里需要一張流程圖

遺留問題

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嚷闭,一起剝皮案震驚了整個濱河市攒岛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胞锰,老刑警劉巖灾锯,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嗅榕,居然都是意外死亡顺饮,警方通過查閱死者的電腦和手機吵聪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兼雄,“玉大人吟逝,你說我怎么就攤上這事∩饫撸” “怎么了块攒?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長佃乘。 經(jīng)常有香客問我囱井,道長,這世上最難降的妖魔是什么趣避? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任庞呕,我火速辦了婚禮,結(jié)果婚禮上程帕,老公的妹妹穿的比我還像新娘住练。我一直安慰自己,他們只是感情好愁拭,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布讲逛。 她就那樣靜靜地躺著,像睡著了一般敛苇。 火紅的嫁衣襯著肌膚如雪妆绞。 梳的紋絲不亂的頭發(fā)上顺呕,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天枫攀,我揣著相機與錄音,去河邊找鬼株茶。 笑死来涨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的启盛。 我是一名探鬼主播蹦掐,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼僵闯!你這毒婦竟也來了卧抗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤鳖粟,失蹤者是張志新(化名)和其女友劉穎社裆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體向图,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡泳秀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年标沪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗜傅。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡金句,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吕嘀,到底是詐尸還是另有隱情违寞,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布偶房,位于F島的核電站坞靶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蝴悉。R本人自食惡果不足惜彰阴,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拍冠。 院中可真熱鬧尿这,春花似錦、人聲如沸庆杜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晃财。三九已至叨橱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間断盛,已是汗流浹背罗洗。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钢猛,地道東北人伙菜。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像命迈,于是被迫代替她去往敵國和親贩绕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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