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è)類(lèi),在類(lèi)中創(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è)類(lèi)中,實(shí)例方法存儲(chǔ)在類(lèi)中纹冤,類(lèi)方法存儲(chǔ)在元類(lèi)中洒宝;
那么當(dāng)我們用調(diào)用類(lèi)方法的方式去調(diào)用實(shí)例方法,它也會(huì)執(zhí)行成功萌京;那為什么用類(lèi)方法的方式去調(diào)用實(shí)例方法雁歌,它也會(huì)成功呢?

答案在一張圖中:

isa流程圖.png

isa的走位中知残,根元類(lèi)最后也會(huì)走到NSObject靠瞎,因此,我們可以用調(diào)用類(lèi)方法的方式去調(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());
        }

上面的一塊代碼中俭厚,作用很明顯,層層遞歸初始化所有方法的類(lèi);

知道這個(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類(lèi)型的*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)容,就可以打印該類(lèi)中的所有方法。

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

我們來(lái)探索一下:
首先創(chuàng)建一個(gè)Person必搞,里面創(chuàng)建一個(gè)方法必指,在對(duì)Person創(chuàng)建一個(gè)分類(lèi)。分類(lèi)中創(chuàng)建一個(gè)相同的方法恕洲,在main函數(shù)中調(diào)用塔橡,得到的結(jié)果是打印的是分類(lèi)方法。這邊就不貼代碼了研侣,實(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ì)去父類(lèi)去找,在父類(lèi)中快速查找沒(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)在父類(lèi)中快速查找中沒(méi)有找到方法敬察,那么它不會(huì)繼續(xù)遞歸進(jìn)行慢速查找,而是繼續(xù)找父類(lèi)的父類(lèi)(NSObject)的緩存中進(jìn)行快速查找尔当,當(dāng)在NSObject的緩存中沒(méi)有找到莲祸,那么繼續(xù)找NSObject的父類(lèi)nil的緩存,進(jìn)入一個(gè)死循環(huán)過(guò)程椭迎。

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

STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0
    CacheLookup GETIMP, _cache_getImp

可以看到它的CacheLookupGETIMP類(lèi)型;因此弄兜,在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返回视卢。

那么如果在父類(lèi)中還是沒(méi)有找到踱卵,那么在之前就會(huì)對(duì)(curClass = curClass->superclass) == nil)會(huì)判斷,將imp賦值為forward_imp,之后會(huì)執(zhí)行這一句判斷imp == forward_imp,相等的話(huà)据过,就會(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)決議钠怯,那么類(lèi)方法的動(dòng)態(tài)決議該如何實(shí)現(xiàn)呢佳魔?
類(lèi)方法存在元類(lèi)中,那么我們?cè)谔砑訉?duì)象時(shí)晦炊,需要將類(lèi)對(duì)象替換成元類(lèi)對(duì)象鞠鲜,而元類(lèi)一層一層往上走到最后,它最后走到的也是NSObject中刽锤,因此镊尺,我們可以在實(shí)例方法中利用if去進(jìn)行判斷,將類(lèi)方法的動(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是類(lèi)方法宋彼,在代碼中弄砍,區(qū)別很明顯仙畦,類(lèi)方法的對(duì)象是是要獲得該類(lèi)的元類(lèi)。

上面的代碼就是我們對(duì)中間層的處理音婶,當(dāng)系統(tǒng)沒(méi)有在類(lè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)系作者
  • 序言:七十年代末婶博,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子荧飞,更是在濱河造成了極大的恐慌凡人,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叹阔,死亡現(xiàn)場(chǎng)離奇詭異挠轴,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)条获,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)忠荞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人帅掘,你說(shuō)我怎么就攤上這事委煤。” “怎么了修档?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵碧绞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我吱窝,道長(zhǎng)讥邻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任院峡,我火速辦了婚禮兴使,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘照激。我一直安慰自己发魄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著励幼,像睡著了一般汰寓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苹粟,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天有滑,我揣著相機(jī)與錄音,去河邊找鬼嵌削。 笑死毛好,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的苛秕。 我是一名探鬼主播睛榄,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼想帅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起啡莉,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤港准,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后咧欣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體浅缸,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年魄咕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了衩椒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哮兰,死狀恐怖毛萌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喝滞,我是刑警寧澤阁将,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站右遭,受9級(jí)特大地震影響做盅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窘哈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一吹榴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滚婉,春花似錦图筹、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)劝篷。三九已至,卻和暖如春民宿,著一層夾襖步出監(jiān)牢的瞬間娇妓,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工活鹰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哈恰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓志群,卻偏偏與公主長(zhǎng)得像着绷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锌云,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354