iOS-慢速方法查找

iOS-快速方法查找中房官,如果沒有找到方法實(shí)現(xiàn)茴厉,最終都會(huì)走到__objc_msgSend_uncached匯編函數(shù),匯編源碼實(shí)現(xiàn)

.macro MethodTableLookup
STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p15 is the class to search
    
    MethodTableLookup//方法列表查找
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached

.endmacro

MethodTableLookup源碼,核心為_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
    mov x3, #3
    bl  _lookUpImpOrForward //跳轉(zhuǎn)執(zhí)行_lookUpImpOrForward

    // IMP in x0
    mov x17, x0

    RESTORE_REGS MSGSEND

.endmacro

當(dāng)我們?cè)诋?dāng)前文件繼續(xù)搜索_lookUpImpOrForward,發(fā)現(xiàn)找不到其實(shí)現(xiàn)预明。全局搜索呢匆光?

全局搜索搜索結(jié)果

發(fā)現(xiàn)全是調(diào)用,跳轉(zhuǎn)昧狮,那么這個(gè)方法實(shí)現(xiàn)去哪里了呢。這里有個(gè)小知識(shí)點(diǎn):

注:
1萧诫、C/C++中調(diào)用 匯編 斥难,去查找匯編時(shí),C/C++調(diào)用的方法需要多加一個(gè)下劃線
2帘饶、匯編 中調(diào)用 C/C++方法時(shí)哑诊,去查找C/C++方法,需要將匯編調(diào)用的方法去掉一個(gè)下劃線

最終我們?cè)?code>objc-runtime-new中找到了該方法實(shí)現(xiàn)及刻,終于又回到了熟悉的c/c++代碼(匯編再見)

image.png

我們來康康源碼吧

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

    if (slowpath(!cls->isInitialized())) {
        // The first message sent to a class is often +new or +alloc, or +self
        // which goes through objc_opt_* or various optimized entry points.
        //
        // However, the class isn't realized/initialized yet at this point,
        // and the optimized entry points fall down through objc_msgSend,
        // which ends up here.
        //
        // We really want to avoid caching these, as it can cause IMP caches
        // to be made with a single entry forever.
        //
        // Note that this check is racy as several threads might try to
        // message a given class for the first time at the same time,
        // in which case we might cache anyway.
        behavior |= LOOKUP_NOCACHE;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.lock();

    // We don't want people to be able to craft a binary blob that looks like
    // a class but really isn't one and do a CFI attack.
    //
    // To make these harder we want to make sure this is a class that was
    // either built into the binary or legitimately registered through
    // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
    
    //檢測(cè)類是否已經(jīng)注冊(cè)到內(nèi)存中
    checkIsKnownClass(cls);
    
    //確認(rèn)繼承鏈镀裤,方便后面方法查找
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;

    // The code used to lookup the class's cache again right after
    // we take the lock but for the vast majority of the cases
    // evidence shows this is a miss most of the time, hence a time loss.
    //
    // The only codepath calling into this without having performed some
    // kind of cache lookup is class_getInstanceMethod().
    
    //死循環(huán)竞阐,死循環(huán)會(huì)用break,goto跳出循環(huán)
    for (unsigned attempts = unreasonableClassCount();;) {
        //再次查找共享緩存暑劝,防止其他線程存入方法
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            //查找方法列表骆莹,getMethodNoSuper_nolock用的二分查找
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }
            //將curClass賦值為superclass并判斷是否為nil,后面就進(jìn)入了父類的查找啦铃岔,從這里可以看出是延繼承鏈倒著往上查找
            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);
        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;
        }
    }

    // No implementation found. Try method resolver once.

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        //寫入緩存汪疮,會(huì)調(diào)用cache.insert方法,注意這里不管是在父類找到imp還是本來imp最終都會(huì)緩存到本類
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

源碼中關(guān)鍵地方我都添加了中文注釋毁习,總結(jié)一下慢速查找流程


慢速查找流程

方法列表查找過程中有個(gè)二分查找智嚷,相對(duì)有趣,貼一下源碼

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;

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    //通過右移一位達(dá)到二分查找的目的
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        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))) {
                probe--;
            }
            return &*probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

對(duì)比_objc_msgSend,_cache_getImp

匯編函數(shù)_objc_msgSend,_cache_getImp實(shí)現(xiàn), 中間調(diào)用CacheLookup時(shí)參數(shù)不同, 導(dǎo)致最后處理分支情況不一樣, 分析如下: .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant

_objc_msgSend緩存未命中情況下, 最終會(huì)調(diào)用lookUpImpOrForward查找流程
ENTRY _objc_msgSend
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

_objc_msgSend 緩存未命中
-> CacheLookup
-> MissLabelDynamic(__objc_msgSend_uncached)
-> MethodTableLookup
-> lookUpImpOrForward
_cache_getImp緩存未命中情況下, 最終會(huì)返回imp為0, 而不回去調(diào)用lookUpImpOrForward, 這點(diǎn)需要注意.
回到查找流程中,查找父類的imp代碼imp = cache_getImp(curClass, sel);, 未命中情況下如果不是返回0, 而是返回forward_imp的話, 直接break; 也就是說只找第一個(gè)superclass的緩存就結(jié)束了, 而不會(huì)繼續(xù)查找第一個(gè)superclass方法列表, 也不會(huì)查找到superclass鏈上的其他類了. 反向驗(yàn)證了, 蘋果這么實(shí)現(xiàn)_cache_getImp的道理.

STATIC_ENTRY _cache_getImp
CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant

_cache_getImp 緩存未命中
-> CacheLookup
-> MissLabelDynamic(LGetImpMissDynamic)
-> ret x0 // x0的值為0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纺且,一起剝皮案震驚了整個(gè)濱河市盏道,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌载碌,老刑警劉巖猜嘱,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嫁艇,居然都是意外死亡朗伶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門步咪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來论皆,“玉大人,你說我怎么就攤上這事猾漫〉闱纾” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵悯周,是天一觀的道長(zhǎng)粒督。 經(jīng)常有香客問我,道長(zhǎng)禽翼,這世上最難降的妖魔是什么屠橄? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮闰挡,結(jié)果婚禮上锐墙,老公的妹妹穿的比我還像新娘。我一直安慰自己解总,他們只是感情好贮匕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著花枫,像睡著了一般刻盐。 火紅的嫁衣襯著肌膚如雪掏膏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天敦锌,我揣著相機(jī)與錄音馒疹,去河邊找鬼。 笑死乙墙,一個(gè)胖子當(dāng)著我的面吹牛颖变,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播听想,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼腥刹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了汉买?” 一聲冷哼從身側(cè)響起衔峰,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛙粘,沒想到半個(gè)月后垫卤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡出牧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年穴肘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舔痕。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡评抚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赵讯,到底是詐尸還是另有隱情盈咳,我是刑警寧澤耿眉,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布边翼,位于F島的核電站,受9級(jí)特大地震影響鸣剪,放射性物質(zhì)發(fā)生泄漏组底。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一筐骇、第九天 我趴在偏房一處隱蔽的房頂上張望债鸡。 院中可真熱鬧,春花似錦铛纬、人聲如沸厌均。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棺弊。三九已至晶密,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間模她,已是汗流浹背稻艰。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侈净,地道東北人尊勿。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像畜侦,于是被迫代替她去往敵國(guó)和親元扔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

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