iOS底層探索之objc_msgSend流程——慢速查找

在上一篇iOS底層探索之objc_msgSend流程——快速查找文章中,我們分析了快速查找流程,如果快速查不到,則需要進(jìn)入慢速查找流程,以下是慢速查找的分析過程

objc_msgSend慢速查找流程

在快速查找流程中,如果沒有找到方法實現(xiàn),無論是走到CheckMiss或者JumpMiss,最終會走到__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
TailCallFunctionPointer x17

END_ENTRY __objc_msgSend_uncached

通過分析__objc_msgSend_uncached的核心是MethodTableLookup查找方法列表。而MethodTableLookup的實現(xiàn)的核心源碼為_lookUpImpOrForward

通過斷點調(diào)試看是不是會走lookUpImpOrForward這個函數(shù),于是我們在那個調(diào)用方法時進(jìn)行斷點調(diào)試:

在調(diào)用方法時加一個斷點,執(zhí)行斷住,按住control + stepinto,進(jìn)入?yún)R編

image.png

_objc_msgSend_uncached加一個斷點,執(zhí)行斷住,按住control + stepinto,進(jìn)入?yún)R編
image.png

發(fā)現(xiàn)lookUpImpOrForward并不是匯編實現(xiàn)而是C++實現(xiàn)。于是我們通過查找源碼,全局搜索lookUpImpOrForward。

lookUpImpOrForward分析

最終在objc-runtime-new.mm的文件中找到了源碼實現(xiàn),源碼如下:

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    //定義的消息轉(zhuǎn)發(fā)
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    //快速查找,如果找到則直接返回imp
    //目的:防止多線程操作時,剛好調(diào)用函數(shù),此時緩存進(jìn)來了
    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

//加鎖,目的是保證讀取的線程安全
    runtimeLock.lock();

    //判斷是否是一個已知類:判斷當(dāng)前類是否是已經(jīng)被認(rèn)可的類,即已經(jīng)加載的類
    checkIsKnownClass(cls);

    //判斷類是否實現(xiàn),如果沒有,需要先實現(xiàn),此時的目的是為了確定父類鏈,方法后續(xù)的循環(huán)。
    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.assertLocked();
    curClass = cls;

    //--查找類的緩存
    //unreasonableClassCount--表示類的迭代的上限
    //(猜測這里遞歸的原因是attempts在第一次循環(huán)時作了減一操作,然后再次循環(huán)時,仍在上限的范圍內(nèi),所以可以繼續(xù)遞歸)
    for (unsigned attempts = unreasonableClassCount();;) {
        //--當(dāng)前類方法列表(采用二分查找算法),如果找到,則返回,將方法緩存到cache中。
        // curClass method list.
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        //當(dāng)前類 - 當(dāng)前類的父類,并判斷父類是否nil
        if (slowpath((curClass = curClass->superclass) == nil)) {
            //--未找到方法實現(xiàn),方法解析器也不行,使用轉(zhuǎn)發(fā)
            imp = forward_imp;
            break;
        }

        //如果父類鏈中存在循環(huán),則停止
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }
        //objc_msgSend->二分查找->cache_fill寫入緩存->objc_msgSend
       // --父類緩存
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // 如果在父類中找到了forward,則停止查找,且不緩存,首先調(diào)用此類的方法解析器
            break;
        }
        if (fastpath(imp)) {
          //如果在父類中,找到了此方法,將其存儲到cache中
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
   //沒有找到方法實現(xiàn),嘗試一次方法解析
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        //動態(tài)方法決議的控制條件,表示流程只走一次
        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;
}

forward_imp的賦值

forward_imp是通過_objc_msgForward_impcache函數(shù)賦值的。全局搜索一下。發(fā)現(xiàn)這個函數(shù)是個匯編函數(shù),找到其實現(xiàn):

//入口
STATIC_ENTRY __objc_msgForward_impcache

//跳轉(zhuǎn)__objc_msgForward
b   __objc_msgForward

END_ENTRY __objc_msgForward_impcache

//__objc_msgForward入口
ENTRY __objc_msgForward
//賦值并且返回x17的地址。
adrp    x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
    
END_ENTRY __objc_msgForward

根據(jù)源碼分析重點__objc_forward_handler

objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

看著objc_defaultForwardHandler很眼熟,這就是我們在日常開發(fā)中最常見的錯誤:沒有實現(xiàn)函數(shù),運行程序,崩潰時報的錯誤提示。
forward_imp的值就是未找到imp的函數(shù)實現(xiàn)

cache緩存中進(jìn)行查找

if (fastpath(behavior & LOOKUP_CACHE)) { 
      imp = cache_getImp(cls, sel);
      if (imp) goto done_nolock;
}

這里的目的是防止多線程操作時,剛好調(diào)用了函數(shù),此時緩存進(jìn)來了,_cache_getImp快速查找,如果找到直接返回imp。

STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0
    CacheLookup GETIMP, _cache_getImp

LGetImpMiss:
    mov p0, #0
    ret

    END_ENTRY _cache_getImp

檢查類是否合法

 checkIsKnownClass(cls);

確保cls在已知類列表中,避免傳入一個非類的二進(jìn)制文件,進(jìn)行CFI攻擊。

確定當(dāng)前類的繼承鏈和isa的繼承鏈

確定當(dāng)前類父類,并且遞歸執(zhí)行,最終確定類的繼承鏈;
確定元類,并把元類的繼承鏈也遞歸確定下來;
initlize時,執(zhí)行addSubClass(),將子類也存入父類中,從而實現(xiàn)了雙向鏈表

static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}

static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();

    if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
        // Non-Swift class. Realize it now with the lock still held.
        // fixme wrong in the future for objc subclasses of swift classes
        realizeClassWithoutSwift(cls, nil);
        if (!leaveLocked) lock.unlock();
    } else {
        // Swift class. We need to drop locks and call the Swift
        // runtime to initialize it.
        lock.unlock();
        cls = realizeSwiftClass(cls);
        ASSERT(cls->isRealized());    // callback must have provoked realization
        if (leaveLocked) lock.lock();
    }

    return cls;
}

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    ASSERT(cls == remapClass(cls));

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

#if FAST_CACHE_META
    if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        cls->setInstancesRequireRawIsa();
    } else {
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        if (DisableNonpointerIsa) {
            instancesRequireRawIsa = true;
        }
        else if (!hackedDispatch  &&  0 == strcmp(ro->name, "OS_object"))
        {
            hackedDispatch = true;
            instancesRequireRawIsa = true;
        }
        else if (supercls  &&  supercls->superclass  &&
                 supercls->instancesRequireRawIsa())
        {
            instancesRequireRawIsa = true;
            rawIsaIsInherited = true;
        }

        if (instancesRequireRawIsa) {
            cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
        }
    }
#endif

    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    cls->setInstanceSize(ro->instanceSize);

    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    methodizeClass(cls, previously);
    //cls->雙向鏈表結(jié)構(gòu)
    return cls;
}

進(jìn)入for死循環(huán)按照類的繼承鏈或者元類的繼承鏈的順序查找。

當(dāng)前cls的方法列表中使用二分查找算法查找方法,如果找到,則進(jìn)入cache寫入流程,并返回imp,如果沒有找到,則返回nil

當(dāng)前cls被賦值為父類,如果父類等于nil,則imp = 消息轉(zhuǎn)發(fā),并終止遞歸,進(jìn)入判斷是否執(zhí)行過動態(tài)方法解析:如果沒有,則執(zhí)行動態(tài)方法解析;如果執(zhí)行過一次動態(tài)方法解析,則走到消息轉(zhuǎn)發(fā)流程。

如果父類鏈中存在循環(huán),則報錯,終止循環(huán)。父類緩存中查找方法, 如果未找到,則直接返回nil,繼續(xù)循環(huán)查找; 如果找到,則直接返回imp,執(zhí)行cache寫入流程

getMethodNoSuper_nolock 方法
getMethodNoSuper_nolock查找本類中的方法列表

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

    ASSERT(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
        // caller of search_method_list, inlining it turns
        // getMethodNoSuper_nolock into a frame-less function and eliminates
        // any store from this codepath.
        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)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        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;
    //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)后出的原則,分類方法最先出,而我們要取的類方法,所以需要先排除分類方法)
                //如果是兩個分類,就看誰先進(jìn)行加載
                probe--;
            }
            return (method_t *)probe;
        }
         //如果keyValue 大于 probeValue,就往probe即中間位置的右邊查找
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

進(jìn)入動態(tài)方法決議

上面for循環(huán)跳出來之后,說明沒有找到imp,會進(jìn)入動態(tài)方法決議。

 if (slowpath(behavior & LOOKUP_RESOLVER)) {
        //動態(tài)方法決議的控制條件,表示流程只走一次
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

查找到imp結(jié)果

當(dāng)我們找到imp時,就會直接進(jìn)入done流程,將cls的sel和imp寫入緩存中(便于下次同樣的方法,可以在cache匯編快速查詢到)。

done結(jié)束后,我們會進(jìn)入done_nolock流程, 如果imp是forward_imp,就返回nil,否則,返回正常的imp。

done:
    // 從將sel和imp寫入cls的緩存
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    // 運行時解鎖
    runtimeLock.unlock();

 done_nolock:
    //如果不需要找了(LOOKUP_NIL),并且imp等于forward_imp,就返回nil。
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    // 返回當(dāng)前獲取的imp
    return imp;

總結(jié)

當(dāng)在objc_msgSend緩存中沒有找到方法,就會來到CheckMiss -> __objc_msgSend_uncached -> MethodTableLookup -> lookUpImpOrForward進(jìn)行慢速查找流程。lookUpImpOrForward中先在本類中查找方法,如果沒有找到就會循環(huán)的去父類當(dāng)中找,直到找到NSObject中,如果沒有找到,就會進(jìn)行動態(tài)方法決議,動態(tài)方法不處理,則進(jìn)入動態(tài)消息轉(zhuǎn)發(fā)階段。此時可以在動態(tài)消息轉(zhuǎn)發(fā)階段做一下處理,如果還不進(jìn)行處理,最后崩潰報錯unrecognized selector sent to instance ...。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容