iOS底層-14:消息流程之慢速查找

上篇文章我們分析了objc_msgSend在緩存里查找imp的流程域仇,我們緊接著上一篇分析__objc_msgSend_uncached

__objc_msgSend_uncached
.endmacro

    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
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached

主要是走兩個(gè)方法郁惜,MethodTableLookupTailCallFunctionPointer x17

MethodTableLookup
.macro MethodTableLookup
    
    // push frame
    SignLR
    stp fp, lr, [sp, #-16]!
    mov fp, sp

    // save parameter registers: x0..x8, q0..q7
    //把參數(shù)寫入到寄存器
    sub sp, sp, #(10*8 + 8*16)
    stp q0, q1, [sp, #(0*16)]
    stp q2, q3, [sp, #(2*16)]
    stp q4, q5, [sp, #(4*16)]
    stp q6, q7, [sp, #(6*16)]
    stp x0, x1, [sp, #(8*16+0*8)]
    stp x2, x3, [sp, #(8*16+2*8)]
    stp x4, x5, [sp, #(8*16+4*8)]
    stp x6, x7, [sp, #(8*16+6*8)]
    str x8,     [sp, #(8*16+8*8)]

    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    bl  _lookUpImpOrForward

    // IMP in x0
    mov x17, x0
    
    // restore registers and return
    //讀取寄存器里的參數(shù)
    ldp q0, q1, [sp, #(0*16)]
    ldp q2, q3, [sp, #(2*16)]
    ldp q4, q5, [sp, #(4*16)]
    ldp q6, q7, [sp, #(6*16)]
    ldp x0, x1, [sp, #(8*16+0*8)]
    ldp x2, x3, [sp, #(8*16+2*8)]
    ldp x4, x5, [sp, #(8*16+4*8)]
    ldp x6, x7, [sp, #(8*16+6*8)]
    ldr x8,     [sp, #(8*16+8*8)]

    mov sp, fp
    ldp fp, lr, [sp], #16
    AuthenticateLR

.endmacro

主要代碼是跳轉(zhuǎn)到_lookUpImpOrForward著隆。

ldp/stp是 ldr/str 的衍生, 可以同時(shí)讀/寫兩個(gè)寄存器, ldr/str只能讀寫一個(gè)
sub sp, sp, #0x20 ; 拉伸椩浼撸空間32(20 = 2*16)個(gè)字節(jié)
stp x0 , x1, [sp, #0x10] ; sp往上加16(10 = 1 * 16)個(gè)字節(jié),存放x0 和 x1
ldp x1 , x0, [sp, #0x10] ; 將sp偏移16個(gè)字節(jié)的值取出來,放入x1 和 x0

_lookUpImpOrForward

直接搜索_lookUpImpOrForward發(fā)現(xiàn)找不到源碼,這里拓展一個(gè)知識點(diǎn)食磕。


在斷點(diǎn)處Debug -> Debug Workflow ->Always show Disassembly查看寄存器信息

objc_msgSend處打上斷點(diǎn)尽棕,按住control + step into,進(jìn)入libobjc.A.dylib`objc_msgSend:找到_objc_msgSend_uncached


按住control + step into彬伦,進(jìn)入libobjc.A.dylib`_objc_msgSend_uncached:

向下查找可找到這里lookUpImpOrForward實(shí)際位置是在objc-runtime-new.mm:6095

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

    // 重新查找一次緩存滔悉,避免多線程的影響
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

    runtimeLock.lock();

   //檢查類是否是已知的類
    checkIsKnownClass(cls);

    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

        // If sel == initialize, class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    runtimeLock.assertLocked();
    curClass = cls;

    // The code used to lookpu 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().

    for (unsigned attempts = unreasonableClassCount();;) {
        // 在類中查找imp,沒有返回nil单绑,不找父類
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        //curClass = curClass->superclass
        //查找繼承鏈回官,直到nil;沒有找到的話賦值forward_imp
        if (slowpath((curClass = curClass->superclass) == 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.");
        }

        // curClass指向父類時(shí)搂橙,從緩存中查找一次imp
        imp = cache_getImp(curClass, sel);
        //imp == forward_imp 跳出循環(huán)
        if (slowpath(imp == forward_imp)) {
            break;
        }
        //查找到了 imp 跳轉(zhuǎn)到done
        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:
    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.為避免多線程影響歉提,進(jìn)來先cache_getImp從緩存中取一次,找到了直接返回imp
2.檢查cls是否是IsKnownClass
3.從當(dāng)前類開始在繼承鏈中查找imp区转,找到就返回imp苔巨;沒找到就返回forward_imp

  • getMethodNoSuper_nolock源碼
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());
    //獲取類的method_list
    auto const methods = cls->data()->methods();
    //mlists是數(shù)組首元素 end是第二個(gè)元素
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }
    return nil;
}
  • search_method_list_inline
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
        //在有序MethodList查找
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // 遍歷mlist 從第一個(gè)到最后一個(gè)废离,如果sel相等侄泽,返回元素地址
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }
#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif
    return nil;
}
  • findMethodInSortedMethodList
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(list);

    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            //找到的是category加進(jìn)來的方法時(shí),將指針向前移動一位
            //默認(rèn)category寫的方法存在前面蜻韭,類本身在后面一位
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

findMethodInSortedMethodList是一個(gè)二分法查找過程悼尾,找到返回method_t,沒有找到返回nil肖方。到這里查找的流程就結(jié)束了闺魏,我們接著看一下找到imp之后的處理。

注意:有category添加方法時(shí)窥妇,要指針向前平移一位舷胜,獲得返回值

  • 回到lookUpImpOrForward里的done
done:
    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;

查找到imp時(shí),調(diào)用log_and_fill_cache然后調(diào)用cache_fill把方法存入cache_t中活翩。與前面內(nèi)容呼應(yīng)上了。

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
    cache_fill(cls, sel, imp, receiver);
}
消息轉(zhuǎn)發(fā)

沒有查找到imp的時(shí)候翻伺,并不是返回nil材泄,而是返回forward_imp

  const IMP forward_imp = (IMP)_objc_msgForward_impcache;

全局搜索_objc_msgForward_impcache吨岭,在objc-msg-arm64.s中找到源碼

__objc_msgForward_impcache只是簡單的跳轉(zhuǎn)到__objc_msgForward

  • __objc_msgForward
ENTRY __objc_msgForward
    //將__objc_forward_handler@PAGE地址存到 x17
    adrp    x17, __objc_forward_handler@PAGE
    //x17偏移__objc_forward_handler@PAGEOFF加載到p17
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward

主要研究的還是__objc_forward_handler方法拉宗。全局搜索__objc_forward_handler發(fā)現(xiàn)找不到源碼,__objc_forward_handler其實(shí)是用C寫的,去掉一個(gè)下劃線旦事,全局搜索魁巩。


這正是我們經(jīng)常找不到方法的報(bào)錯。

unrecognized selector sent to instance 0x101484880

動態(tài)方法決議

沒有找到imp時(shí)姐浮,并不是直接報(bào)錯谷遂,還做了其他處理。


imp被賦值為forward_imp時(shí)卖鲤,break跳出循環(huán)肾扰,會走到resolveMethod_locked方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛋逾,一起剝皮案震驚了整個(gè)濱河市集晚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌区匣,老刑警劉巖偷拔,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異亏钩,居然都是意外死亡莲绰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門铸屉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钉蒲,“玉大人,你說我怎么就攤上這事彻坛∏晏洌” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵昌屉,是天一觀的道長钙蒙。 經(jīng)常有香客問我,道長间驮,這世上最難降的妖魔是什么躬厌? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮竞帽,結(jié)果婚禮上扛施,老公的妹妹穿的比我還像新娘。我一直安慰自己屹篓,他們只是感情好疙渣,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堆巧,像睡著了一般妄荔。 火紅的嫁衣襯著肌膚如雪泼菌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天啦租,我揣著相機(jī)與錄音哗伯,去河邊找鬼。 笑死篷角,一個(gè)胖子當(dāng)著我的面吹牛焊刹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播内地,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼伴澄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了阱缓?” 一聲冷哼從身側(cè)響起非凌,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荆针,沒想到半個(gè)月后敞嗡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡航背,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年喉悴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玖媚。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡箕肃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出今魔,到底是詐尸還是另有隱情勺像,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布错森,位于F島的核電站吟宦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涩维。R本人自食惡果不足惜殃姓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瓦阐。 院中可真熱鬧蜗侈,春花似錦、人聲如沸睡蟋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薄湿。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間豺瘤,已是汗流浹背吆倦。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坐求,地道東北人蚕泽。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像桥嗤,于是被迫代替她去往敵國和親须妻。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

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