objc_msgSend慢速查找流程分析

在之前寫(xiě)過(guò)一篇關(guān)于objc_msgSend的流程分析,而它就是方法的快速查找;那么本文將介紹一下方法的慢速查找流程。

objc_msgSend的匯編代碼中,當(dāng)查找isa完畢之后,他會(huì)跳轉(zhuǎn)到CacheLookup的匯編代碼中執(zhí)行,當(dāng)系統(tǒng)在緩存中沒(méi)有找到相應(yīng)的方法,他就會(huì)繼續(xù)跳轉(zhuǎn)到CheckMiss的匯編代碼中。我們來(lái)看一下CheckMiss的源碼:

.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

當(dāng)系統(tǒng)走到這個(gè)過(guò)程中,會(huì)繼續(xù)執(zhí)行NORMAL中的cbz p9, __objc_msgSend_uncached這一行代碼,也就是說(shuō),我們就可以以這個(gè)方法為入口去探索慢速查找的過(guò)程。

我們進(jìn)行全局搜索__objc_msgSend_uncached這個(gè),找到了MethodTableLookup,這個(gè)是一個(gè)方法列表,繼續(xù)搜索這個(gè)方法,我們就只能查找到一個(gè)跳轉(zhuǎn)的bl _lookUpImpOrForward,之后我們就無(wú)法從找到這個(gè)方法的內(nèi)容。

那么按照我們往常的套路,我們把下劃線去掉,經(jīng)過(guò)搜索lookUpImpOrForward這個(gè)方法,找到了很多。

那么我們有沒(méi)有別的途徑去找到這個(gè)方法呢?

我們創(chuàng)建一個(gè)工程,在工程中創(chuàng)建一個(gè)類,在類中創(chuàng)建一個(gè)方法,在調(diào)用方法前打上斷點(diǎn):

16007838857526.png

把這個(gè)模式打開(kāi),可可以看到匯編中我們打上斷點(diǎn)的方法處:


16007840902468.png

在方法下面的objc_msgSend處打上斷點(diǎn):

16007841565462.png

按住control+
16007842149173.png

就進(jìn)入了objc_msgSend方法中去了:

16007842605844.png

繼續(xù)往下滑,就會(huì)看到下面圖的樣子,在_objc_msgSend_uncached處打上斷點(diǎn),繼續(xù)按住control+

16007842149173.png

16007845553851.png

接著就會(huì)跳轉(zhuǎn)到_objc_msgSend_uncached方法中去,就能看到lookUpImpOrForward at objc-runtime-new.mm:6099這個(gè)信息。

16007849452311.png

下面繼續(xù)描述一下方法的一些基本常識(shí):
在一個(gè)類中,實(shí)例方法存儲(chǔ)在中,類方法存儲(chǔ)在元類中;
那么當(dāng)我們用調(diào)用類方法的方式去調(diào)用實(shí)例方法,它也會(huì)執(zhí)行成功;那為什么用類方法的方式去調(diào)用實(shí)例方法,它也會(huì)成功呢?

答案在一張圖中:

isa流程圖.png

isa的走位中,根元類最后也會(huì)走到NSObject,因此,我們可以用調(diào)用類方法的方式去調(diào)用實(shí)例方法。

下面繼續(xù)探索慢速查找流程:
由于在上面我們找到了lookUpImpOrForward這個(gè)方法入口,那么我們就去看看這個(gè)方法的實(shí)現(xiàn)代碼:

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);
    }
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }
    runtimeLock.assertLocked();
    curClass = cls;
        for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        if (slowpath((curClass = curClass->superclass) == nil)) {
            imp = forward_imp;
            break;
        }
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            break;
        }
        if (fastpath(imp)) {
            goto done;
        }
    }
    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;
}

在main函數(shù)中調(diào)用一個(gè)方法,在方法調(diào)用處打上斷點(diǎn),在執(zhí)行程序之后,在if (fastpath(behavior & LOOKUP_CACHE)) {處打上斷點(diǎn),我們點(diǎn)擊下一步,程序就會(huì)跳轉(zhuǎn)到這一步上去;

16008270884210.png

我們發(fā)現(xiàn),快速查找會(huì)查找緩存中的方法,而慢速查找,它還要查找cache_getImp,那這是為什么呢?
答案是因?yàn)橐苊?code>多線程的影響,當(dāng)你程序在調(diào)用時(shí),線程可能偷偷摸摸的緩存了一次。

我們繼續(xù)往下看源碼,在下面有兩個(gè)if slowpath判斷,我們從上面的if (slowpath(!cls->isRealized()))的判斷內(nèi)去看它的實(shí)現(xiàn):

cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
它里面存在一個(gè)方法realizeClassMaybeSwiftAndLeaveLocked,點(diǎn)擊方法進(jìn)去,它返回的也是一個(gè)方法:realizeClassMaybeSwiftMaybeRelock,繼續(xù)點(diǎn)擊,在realizeClassMaybeSwiftMaybeRelock方法中,有一個(gè)關(guān)鍵的方法:realizeClassWithoutSwift,點(diǎn)擊進(jìn)去,里面的代碼很多,貼出一部分源碼:

runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

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

    // fixme verify class is not in an un-dlopened part of the shared cache?

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

在這個(gè)方法中,首先讀clsdata數(shù)據(jù),對(duì)rw,ro進(jìn)行賦值,然后就有了數(shù)據(jù);在有了數(shù)據(jù)之后,我們繼續(xù)讀下一塊代碼:

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

里面有一個(gè)superclsmetacls兩個(gè)遞歸實(shí)現(xiàn),利用realizeClassWithoutSwift,把整個(gè)繼承鏈遞歸下來(lái),這符合那副經(jīng)典的走位圖。繼續(xù)看下一塊代碼:

// Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls, previously);

    return cls;

在這一塊中,有對(duì)cls->superclasscls->initClassIsa進(jìn)行賦值,然后在下面會(huì)對(duì)supercls進(jìn)行判斷,分析這個(gè)if判斷,它是一個(gè)雙向鏈表結(jié)構(gòu),結(jié)下來(lái)的methodizeClass是對(duì)所有列表和屬性都會(huì)添加到rwe當(dāng)中去。

看完realizeClassMaybeSwiftAndLeaveLocked的大部分內(nèi)容之后,我們繼續(xù)往下看另一個(gè)slowpath中的initializeAndLeaveLocked方法;繼續(xù)點(diǎn)擊方法進(jìn)去,最終走到initializeAndMaybeRelock方法,在這個(gè)方法中最重要的一行代碼是調(diào)用的initializeNonMetaClass這個(gè)方法,這個(gè)方法,貼出一塊代碼:

Class supercls;
    bool reallyInitialize = NO;
initialize cls.

    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
            localWillInitializeFuncs.initFrom(willInitializeFuncs);
        }
    }
    if (reallyInitialize) {

        _setThisThreadIsInitializingClass(cls);

        if (MultithreadedForkChild) {
            // LOL JK we don't really call +initialize methods after fork().
            performForkChildInitialize(cls, supercls);
            return;
        }
        
        for (auto callback : localWillInitializeFuncs)
            callback.f(callback.context, cls);
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
            objc_thread_self(), cls->nameForLogging());
        }

上面的一塊代碼中,作用很明顯,層層遞歸初始化所有方法的;

知道這個(gè)作用之后,其他代碼也就不那么重要了,回到lookUpImpOrForward方法,繼續(xù)往下看源碼,它的返回值是imp,既然是返回imp,如果它找不到imp怎么辦呢?
那么我們就去看看imp賦值的地方在哪里,他的重點(diǎn)在for (unsigned attempts = unreasonableClassCount();;)for循環(huán)中,代碼在上面已經(jīng)貼出來(lái)了。我們?nèi)パ芯窟@一段代碼:
在代碼中有一個(gè)方法getMethodNoSuper_nolock獲取cls中的所有方法。
我們來(lái)看一下它的源碼:

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

可以看到cls->data()->methods(),這個(gè)data()就是之前加載過(guò)來(lái)的ro,rw這些東西。

探索到這里,就涉及到一個(gè)算法,所有的方法都在method_list中,那么系統(tǒng)如何去查找方法呢?
答案:二分查找,二分查以中間點(diǎn)為界限,將精確的值確定在某個(gè)范圍內(nèi),經(jīng)過(guò)不斷縮小范圍,查找正確的內(nèi)容;

那么是如何知道系統(tǒng)是通過(guò)二分查找去找到對(duì)應(yīng)的方法呢?
getMethodNoSuper_nolock方法中有一個(gè)對(duì)method_t類型的*m賦值的search_method_list_inline函數(shù),我們?cè)谶@個(gè)函數(shù)中有一個(gè)findMethodInSortedMethodList方法,下面附上這個(gè)方法的源碼:

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.
        while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

在for循環(huán)遍歷所有方法中,probe的賦值中有一個(gè)count >> 1位移操作,其作用是減少二分之一;
例如:8的二進(jìn)制為1000; 8>>1 100 = 4;

通過(guò)上面的代碼,我們可以了解到系統(tǒng)查找方法是使用二分查找來(lái)尋找內(nèi)容。

我們可以在控制臺(tái)中打印list中的內(nèi)容,就可以打印該類中的所有方法。

那么在上面的代碼中,probe--又是怎么理解呢?
它其實(shí)是來(lái)判斷分類重名的作用;

我們來(lái)探索一下:
首先創(chuàng)建一個(gè)Person,里面創(chuàng)建一個(gè)方法,在對(duì)Person創(chuàng)建一個(gè)分類。分類中創(chuàng)建一個(gè)相同的方法,在main函數(shù)中調(diào)用,得到的結(jié)果是打印的是分類方法。這邊就不貼代碼了,實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單。

當(dāng)系統(tǒng)沒(méi)有找到對(duì)應(yīng)的方法之后,它就會(huì)執(zhí)行goto done;代碼:

done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
    

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

那么在這個(gè)代碼中,它會(huì)對(duì)方法進(jìn)行緩存,以方便下次快速查找。

那么整個(gè)流程就是:objc_msgSend -> 二分查找自己 -> cache_fill ->objc_msgSend形成一個(gè)閉環(huán)。
如果在自己中沒(méi)有找到方法,那么它就會(huì)去父類去找,在父類中快速查找沒(méi)找到,他就會(huì)執(zhí)行cache_getImp方法,在經(jīng)過(guò)搜索這個(gè)方法,沒(méi)有找到,那么可以肯定的是,它存在匯編代碼中(只要與緩存相關(guān)的,一般都在匯編代碼中):

STATIC_ENTRY _cache_getImp

GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp

那么它又會(huì)執(zhí)行CacheLookup方法,接著又會(huì)跑到lookUpImpOrForward方法中查找。

注意事項(xiàng):當(dāng)在父類中快速查找中沒(méi)有找到方法,那么它不會(huì)繼續(xù)遞歸進(jìn)行慢速查找,而是繼續(xù)找父類的父類(NSObject)的緩存中進(jìn)行快速查找,當(dāng)在NSObject的緩存中沒(méi)有找到,那么繼續(xù)找NSObject的父類nil的緩存,進(jìn)入一個(gè)死循環(huán)過(guò)程。

那為什么在父類中沒(méi)有慢速查找過(guò)程呢?
由于imp的獲取在cache_getImp中,通過(guò)全局搜索,它存在匯編當(dāng)中,附上代碼:

STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0
    CacheLookup GETIMP, _cache_getImp

可以看到它的CacheLookupGETIMP類型;因此,在CacheLookup的匯編代碼最后會(huì)在JumpMiss中,附上他的代碼:

.macro JumpMiss
.if $0 == GETIMP
    b   LGetImpMiss
.elseif $0 == NORMAL
    b   __objc_msgSend_uncached
.elseif $0 == LOOKUP
    b   __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

可以看到,只有當(dāng)CacheLookupNORMAL時(shí),才會(huì)進(jìn)行慢速查找。
LGetImpMiss的代碼中,它最后只是進(jìn)行return返回。

那么如果在父類中還是沒(méi)有找到,那么在之前就會(huì)對(duì)(curClass = curClass->superclass) == nil)會(huì)判斷,將imp賦值為forward_imp,之后會(huì)執(zhí)行這一句判斷imp == forward_imp,相等的話,就會(huì)遞歸出去,跳出循環(huán)。

那么這個(gè)forward_imp又是什么東西呢?
在上面的代碼中,forward_imp是通過(guò)_objc_msgForward_impcache方法進(jìn)行賦值的,我們?nèi)タ纯催@個(gè)方法,經(jīng)過(guò)全局搜索,它存在匯編中,代碼我就不貼出來(lái)的,直接寫(xiě)出關(guān)鍵方法__objc_forward_handler,搜索_objc_forward_handler,就得到一個(gè)很有意思的東西:

__attribute__((noreturn, cold)) void
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);
}

上面的代碼,相信很多iOS開(kāi)發(fā)者都見(jiàn)過(guò)。當(dāng)方法找不到時(shí),就會(huì)顯示這個(gè)錯(cuò)誤。

那么在報(bào)錯(cuò)之前,它還會(huì)走到下面這部分代碼中:

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

那么我們就去看看這部分代碼中的內(nèi)容,首先點(diǎn)擊resolveMethod_locked,他是一個(gè)動(dòng)態(tài)方法決議;動(dòng)態(tài)方法決議其實(shí)是系統(tǒng)在沒(méi)有找到方法時(shí),它給你一個(gè)處理過(guò)程,處理完之后,它再繼續(xù)去查找,只要能找到方法所需要的內(nèi)容,就不會(huì)報(bào)錯(cuò),如果沒(méi)有,則報(bào)出錯(cuò)誤。
下面去看看它的源碼:

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

在上面一段代碼中,重要的就在那個(gè)if判斷中,我們?nèi)タ纯?code>resolveInstanceMethod方法,請(qǐng)看源碼:

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

這段源碼作用就是只要能找到這個(gè)方法的實(shí)現(xiàn),那么它就會(huì)去執(zhí)行這個(gè)方法,動(dòng)態(tài)方法協(xié)議其實(shí)是蘋(píng)果給我們的一個(gè)機(jī)會(huì),在快速和慢速都沒(méi)找到相應(yīng)的方法,那么它就給你一個(gè)去實(shí)現(xiàn)這個(gè)方法的機(jī)會(huì),只要系統(tǒng)找到了這個(gè)方法,那么他就能執(zhí)行成功。

下面我們用代碼來(lái)試一下:
在聲明文件中創(chuàng)建一個(gè)方法,在實(shí)現(xiàn)文件中不去寫(xiě)這個(gè)方法內(nèi)容,那么我們?cè)趍ain函數(shù)中執(zhí)行,它會(huì)報(bào)錯(cuò),那么我們用另一種方式去寫(xiě),就不會(huì)報(bào)錯(cuò)了:

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    
    if (sel == @selector(say666)) {
        NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));
        
        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }
    
    return [super resolveInstanceMethod:sel];
}

上面的代碼是實(shí)例方法的動(dòng)態(tài)決議,那么類方法的動(dòng)態(tài)決議該如何實(shí)現(xiàn)呢?
類方法存在元類中,那么我們?cè)谔砑訉?duì)象時(shí),需要將類對(duì)象替換成元類對(duì)象,而元類一層一層往上走到最后,它最后走到的也是NSObject中,因此,我們可以在實(shí)例方法中利用if去進(jìn)行判斷,將類方法的動(dòng)態(tài)決議也寫(xiě)在實(shí)例方法中,請(qǐng)看代碼:

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    

    NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));
    if (sel == @selector(say666)) {
        NSLog(@"%@ 來(lái)了",NSStringFromSelector(sel));

        IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
        Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(self, sel, imp, type);
    }
    else if (sel == @selector(sayNB)) {
        
        IMP imp           = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);
    }
    return NO;
}

在上面的代碼中,say666是實(shí)例方法,sayNB是類方法,在代碼中,區(qū)別很明顯,類方法的對(duì)象是是要獲得該類的元類。

上面的代碼就是我們對(duì)中間層的處理,當(dāng)系統(tǒng)沒(méi)有在類中找到這個(gè)方法,那么就會(huì)啟動(dòng)動(dòng)態(tài)決議,給我們一個(gè)處理的過(guò)程,處理完之后,再重新去執(zhí)行lookUpImpOrForward,這樣就不會(huì)報(bào)錯(cuò)了。

重點(diǎn):lookUpImpOrForward這個(gè)方法會(huì)執(zhí)行兩次,第一次是動(dòng)態(tài)方法解析,第二次是慢速轉(zhuǎn)發(fā)過(guò)程。

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

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