08-慢速查找流程

知識(shí)點(diǎn)

1: dirty memory : 臟內(nèi)存, 支持增刪改的內(nèi)存區(qū)域
eg: rw結(jié)構(gòu)體
2: clean memory : 干凈內(nèi)存, 只支持讀的內(nèi)存區(qū)域
eg: ro結(jié)構(gòu)體

為什么這么設(shè)計(jì)呢?

蘋(píng)果的內(nèi)存優(yōu)化操作, 防止干凈內(nèi)存(不經(jīng)常修改的內(nèi)存區(qū)域)受到污染, 比如方法列表, 如果蘋(píng)果不區(qū)分兩塊兒區(qū)域的話(huà), 意味著每次運(yùn)行時(shí)動(dòng)態(tài)添加的方法和分類(lèi)里擴(kuò)展的方法都會(huì)被寫(xiě)入進(jìn)去, 那么就會(huì)導(dǎo)致objc_class整片區(qū)域的內(nèi)存都要受到污染, 導(dǎo)致內(nèi)存增大, 性能耗損. 但是區(qū)分開(kāi)后, 我們只處理dirty memory中的methods列表就會(huì)是內(nèi)存和性能得到很大的提升

詳見(jiàn): Runtime2020升級(jí)改版-更小, 更安全, 更高效

1: 慢速查詢(xún)的進(jìn)入方式

方法1: 匯編最終流程發(fā)現(xiàn)

MethodTableLookup-c856

這里跟lookUpImpOrForward方法實(shí)現(xiàn)中的這塊代碼相對(duì)應(yīng)

// 這里是預(yù)防多線(xiàn)程有可能調(diào)用緩存方法的情況
// 需要注意behavior & LOOKUP_CACHE的結(jié)果
// behavior: cache查找完后會(huì)傳3, &后為0, 會(huì)跳過(guò)cache快速查找
if (fastpath(behavior & LOOKUP_CACHE)) {
    imp = cache_getImp(cls, sel);
    if (imp) goto done_nolock;
}

方法2: 匯編

通過(guò)注釋掉實(shí)現(xiàn), 通過(guò)debug Workflow-> always show disassembly 查看匯編流程, objc_msgSend ->(ctrl + step info) _objc_msgSend_uncached -> lookUpImpOrForward

2: 解析lookUpImpOrForward方法

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();
    // 1.
    // 這里是預(yù)防多線(xiàn)程有可能調(diào)用緩存方法的情況
    // 需要注意behavior & LOOKUP_CACHE的結(jié)果
    // behavior: cache查找完后會(huì)傳3, &后為0, 跳過(guò)cache快速查找
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }
    // 加鎖(保障線(xiàn)程安全)
    runtimeLock.lock();
    
    // 2. 是否是已注冊(cè)驗(yàn)證的Class, 防止代碼被惡意入侵, 注入
    checkIsKnownClass(cls);

    // 3. 檢查是否進(jìn)行了類(lèi)初始化
    if (slowpath(!cls->isRealized())) {
        // 4. 內(nèi)部進(jìn)行了ro和rw等基礎(chǔ)信息的分配, 以及cls作為一個(gè)雙向鏈表的繼承關(guān)系綁定操作
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }
    
    // 5. 檢查是否完成類(lèi)信息的初始化
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        // 6. 內(nèi)部將遞歸初始化所有類(lèi)并自動(dòng)發(fā)送initialize方法
        // 根據(jù)需要將“ + initialize”消息發(fā)送給任何繼承鏈上未初始化的類(lèi)
        // ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

    runtimeLock.assertLocked();
    curClass = cls;

    // 7. 這里的for是死循環(huán), 因?yàn)闆](méi)有設(shè)置循環(huán)結(jié)束條件
    for (unsigned attempts = unreasonableClassCount();;) {
        // 1. 先查找自己的method list里有沒(méi)有, 而不查找父類(lèi)的
        // 內(nèi)部查找方法findMethodInSortedMethodList用到了二分查找算法, 提高查找效率, 詳見(jiàn)注釋
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {//查到了則跳出循環(huán)完成
            imp = meth->imp;
            goto done;
        }
        
        // 2. curClass在這里會(huì)遞歸賦值為它的父類(lèi),直到繼承關(guān)系的最終點(diǎn)nil時(shí), 則會(huì)賦值imp為forward_imp
        if (slowpath((curClass = curClass->superclass) == nil)) {
            imp = forward_imp;
            // 3. 這里是for死循環(huán)的出口條件
            break;
        }

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

        // 4. Superclass cache.
        // 因?yàn)樯厦鎐urClass會(huì)遞歸被賦值為它的父類(lèi), 所以這里查找到的是父類(lèi)的緩存 -> 匯編快速查找cache
        // 注意: cache_getimp 快速查詢(xún)時(shí), 傳入?yún)?shù)是`GETIMP`
        // 如果沒(méi)找到, 最終jumpMiss返回的是`LGetImpMiss`, 即0x0
        imp = cache_getImp(curClass, sel);
        
        // 5. 當(dāng)在父類(lèi)找到的imp等于forward_imp時(shí), 跳出循環(huán)
        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;
        }
        // 6. 如果cache_getImp找到了imp在這跳出循環(huán)完成
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // 8. No implementation found. Try method resolver once.
    // 動(dòng)態(tài)方法決議,
    // behavior & LOOKUP_RESOLVER)類(lèi)似上面, 作用是只調(diào)用一次
    // 待驗(yàn)證: 通過(guò)這里實(shí)現(xiàn)消息轉(zhuǎn)發(fā)的, 這里會(huì)只調(diào)用1次
    //        沒(méi)有實(shí)現(xiàn)消息轉(zhuǎn)發(fā)的, 則會(huì)調(diào)用2次
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        // 9. 動(dòng)態(tài)方法決議
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    // 10. 0將查找到的方法寫(xiě)入緩存, 避免下次調(diào)用還是慢速查找, 并形成一個(gè)閉環(huán)
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

1)重要流程2-4. cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);

OC準(zhǔn)備Class基本信息01-c906
  • 下面這個(gè)方法的實(shí)現(xiàn), 是在慢速查找流程前的重點(diǎn), 它準(zhǔn)備了class信息中ro和rw等基礎(chǔ)信息, 以及雙向鏈表(父類(lèi), 元類(lèi), 子類(lèi))的處理, 確認(rèn)傳入對(duì)象的繼承關(guān)系, 給后面遞歸其父類(lèi)緩存方法的流程提供準(zhǔn)備工作
static Class realizeClassWithoutSwift(Class cls, Class previously)

??

// 雙向鏈表, 確認(rèn)繼承關(guān)系
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
??    
cls->superclass = supercls;
cls->initClassIsa(metacls);
??
// 將此類(lèi)連接到其超類(lèi)的子類(lèi)列表
if (supercls) {
    addSubclass(supercls, cls);
} else {
    addRootClass(cls);
}

2)核心流程2-7-1 Method meth = getMethodNoSuper_nolock(curClass, sel);

查找主類(lèi)或其分類(lèi)的方法列表: 采用二分查找算法, 提高查詢(xún)效率, 減少性能損耗

Method meth = getMethodNoSuper_nolock(curClass, sel);
??
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(list);
    // list: 是個(gè)排完序, 并從小到大遞增的方法列表.eg: 0, 1, 2,
    // &list->first: 取址, 并找到首元素
    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    // 要找的SEL, SEL被強(qiáng)轉(zhuǎn)為uintptr_t, 用來(lái)比較大小
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    // 1: 二分查找
    //count >>= 1 等價(jià)于count/2 16進(jìn)制
    for (count = list->count; count != 0; count >>= 1) {
        //內(nèi)存偏移(base指針地址偏移(count >> 1)個(gè)位置)
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // 這個(gè)當(dāng)sel相同時(shí), 需要循環(huán)遞減查找到它的分類(lèi)的方法實(shí)現(xiàn), 因?yàn)榧虞d到內(nèi)存時(shí), 分類(lèi)會(huì)加載在主類(lèi)的前邊
            // 也可以側(cè)面驗(yàn)證分類(lèi)可以重寫(xiě)并覆蓋主類(lèi)的方法的調(diào)用
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    /* * 舉個(gè)栗子
     **運(yùn)行流程舉例1**: 編譯斷點(diǎn)走一走, 你懂我也懂
     list = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 ,0x8];
     base = 0x1
     keyValue = 0x7;
     運(yùn)行流程舉例1: 編譯斷點(diǎn)走一走, 你懂我也懂
     1: count: 8;  count >> 1: 4;  probe = 0x5(0x1+4);
        1): keyValue > probeValue: true;
            base = 0x6 (probe + 1);  count: 7(count--)
        2): 進(jìn)入下一次循環(huán)
     2: count: 7(--);  count >>= 1: 3;  probe = 0x6 + 1(3>>1): 0x7;
        命中返回.
    -----------------------------------------------------------------
    **運(yùn)行流程舉例2**: 編譯斷點(diǎn)走一走, 你懂我也懂
    list = [0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 ,0x8];
    base = 0x1
    keyValue = 0x2;
    1: count: 8;  probe = 0x5(0x1+4(count>>1));
        1): keyValue > probeValue: false;
        2): 進(jìn)入下一次循環(huán): true
            base = 0x1;  count: 8 (都不變)
    2: count: 8;  count >>= 1: 4;  probe = 0x1 + 2(4>>1): 0x3;
        1): keyValue > probeValue: false;
        2): 進(jìn)入下一次循環(huán): true
            base = 0x1;  count: 4 (都不變)
    3: count: 4;  count >>= 1: 2;  probe = 0x1 + 1(4>>1): 0x2;
        命中返回
     **/
    
    return nil;
}
二分查找圖示-c964

3)核心流程2-8和9. resolveMethod_locked(inst, sel, cls, behavior);

    // 動(dòng)態(tài)方法決議,
    // behavior & LOOKUP_RESOLVER)類(lèi)似上面, 作用是只調(diào)用一次
    // 待驗(yàn)證: 通過(guò)這里實(shí)現(xiàn)消息轉(zhuǎn)發(fā)的, 這里會(huì)只調(diào)用1次
    //        沒(méi)有實(shí)現(xiàn)消息轉(zhuǎn)發(fā)的, 則會(huì)調(diào)用2次
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        // 動(dòng)態(tài)方法決議
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

3. 慢速查找流程圖

慢速查找流程圖
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肺缕,一起剝皮案震驚了整個(gè)濱河市拒迅,隨后出現(xiàn)的幾起案子铝噩,更是在濱河造成了極大的恐慌凄敢,老刑警劉巖氧吐,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件霹菊,死亡現(xiàn)場(chǎng)離奇詭異茅郎,居然都是意外死亡茅茂,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)罢维,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)淹仑,“玉大人,你說(shuō)我怎么就攤上這事肺孵≡冉瑁” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵平窘,是天一觀的道長(zhǎng)吓肋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瑰艘,這世上最難降的妖魔是什么是鬼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮紫新,結(jié)果婚禮上均蜜,老公的妹妹穿的比我還像新娘。我一直安慰自己芒率,他們只是感情好囤耳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著偶芍,像睡著了一般充择。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匪蟀,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天椎麦,我揣著相機(jī)與錄音,去河邊找鬼材彪。 笑死观挎,一個(gè)胖子當(dāng)著我的面吹牛撒桨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播键兜,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼凤类,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了普气?” 一聲冷哼從身側(cè)響起谜疤,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎现诀,沒(méi)想到半個(gè)月后夷磕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仔沿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年坐桩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片封锉。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绵跷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出成福,到底是詐尸還是另有隱情碾局,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布奴艾,位于F島的核電站净当,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蕴潦。R本人自食惡果不足惜像啼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望潭苞。 院中可真熱鬧忽冻,春花似錦、人聲如沸萄传。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)秀菱。三九已至,卻和暖如春蹭睡,著一層夾襖步出監(jiān)牢的瞬間衍菱,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工肩豁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留脊串,地道東北人辫呻。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像琼锋,于是被迫代替她去往敵國(guó)和親放闺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355