iOS-底層原理-消息流程objc_msgSend分析之methodList(慢速查找)、動態(tài)方法決議、消息轉(zhuǎn)發(fā)

1.什么是慢速查找、動態(tài)方法決議、消息轉(zhuǎn)發(fā)

前面的博客介紹了,objs_msgSend查找cache的快速查找流程,即實例對象查找類對象cache,類對象查找元類對象的cache,也就是說,通過isa指向找到第一個查找節(jié)點的cache,進而匯編語言進行快速查找,那么第一個節(jié)點的methodList怎么查找,什么時候查找,后面父類的cache和methodList呢?

父類的cache查找和我們前面分析的第一個節(jié)點的cache查找流程是否相同呢?這里isa和superclass看起來有些關(guān)聯(lián)了,即通過isa確定第一個方法的查找節(jié)點,如果沒有查找父類一直到NSObject到nil為止,其實還是沒什么關(guān)系,各自獨立的指針指向,只不過這種情況需要兩者結(jié)合到一起使用,屬于組隊完成整個流程的過程。

cachemethodList都找不到的情況會有哪些補救措施呢?各級父類的cachemethodList查找以及找不到imp后的流程是這篇文章要分析的重點。

1.查找流程示意
對象方法查找流程
cache(類) ---> methodList(類) ---> cache(父類) ---> methodList(父類) ---> cache(NSObject ) ---> methodList(NSObject ) ---> 動態(tài)方法決議(添加一個imp以method形式返回) ---> 快速轉(zhuǎn)發(fā) --->慢速轉(zhuǎn)發(fā)( 消息轉(zhuǎn)發(fā) )

類方法查找流程
cache(元類) ---> methodList(類) ---> cache(父元類) ---> methodList(父元類) ---> cache(根元NSObject ) ---> methodList(根元類NSObject ) ---> cache(根類NSObject ) ---> methodList(根類NSObject ) ---> 動態(tài)方法決議(添加一個imp以method形式返回) ---> 消息轉(zhuǎn)發(fā) ---> 快速轉(zhuǎn)發(fā) --->慢速轉(zhuǎn)發(fā)

2.慢速查找、動態(tài)方法決議、消息轉(zhuǎn)發(fā)
慢速查找其實就是起個名字,指的是逐級父類查找methodList的過程,快速查找就是匯編查找cache的過程
動態(tài)方法決議就是,沒有對應(yīng)的imp,利用runtime在給定的方法里面動態(tài)添加一個和sel對應(yīng)的imp,用method結(jié)構(gòu)返回,然后再來查找一次,即從第一個節(jié)點的methodList再查找一次,這里猜測應(yīng)該是methodList和cache都被添加進去了

如果動態(tài)方法決議沒有實現(xiàn),走消息轉(zhuǎn)發(fā)流程,快速轉(zhuǎn)發(fā)就是用其他對象或者類來做這個事情

慢速轉(zhuǎn)發(fā)就是通過方法簽名獲得一個任務(wù),保留或者給其他類或者對象轉(zhuǎn)發(fā)都可以,可以簡單理解,這里我把這個sel通過簽名的方法看出一個任務(wù)了,至于怎么做iOS系統(tǒng)就不用管了,知道有這么個事就行了,這樣就不會執(zhí)行崩潰流程了

cache和methodList這種自然的流程如果沒有,看看能不能動態(tài)添加一個,如果也不添加,看看能不能轉(zhuǎn)發(fā)給其他小伙伴處理,其他小伙伴如果也不處理,看看能不能把這個當(dāng)成個事,至于什么時候做或者做不做都沒關(guān)系,只有你口頭承認這是個事,交給我了,iOS系統(tǒng)就不糾結(jié)了,即不執(zhí)行后面的崩潰流程了,如果以上都不執(zhí)行才會走到崩潰的流程里面來,

那么類在自然加載條件下cache和methodList是固定的,如果沒有實現(xiàn)就是不存在imp,動態(tài)方法決議和消息轉(zhuǎn)發(fā)的部分是開發(fā)給iOS開發(fā)者的,我們可以利用這兩個節(jié)點,滿足條件,就可以達到任而東南西北風(fēng),只有是sel查找imp,這種方法找不到 unrecognized selector的崩潰都可以避免掉了,不論對象方法還是類方法都能hook住了,好的一點是無論類繼承鏈還是元類繼承鏈最后都指向了NSObject根類,那我們可以在根類NSObject的分類 category里面搞事情了,達到統(tǒng)一入口,無入侵性,一次性解決unrecognized selector這種崩潰問題,還是很好的,其實動態(tài)方法決議也是蘋果允許人為的向cache或者methodList的加入方法的機會

3.objc_msgSend匯編到c、c++層面的lookUpImpOrForward執(zhí)行流程
_objc_msgSend如果在第一個節(jié)點的cache里面沒有找到imp,則會執(zhí)行如下匯編的代碼段__objc_msgSend_uncached ---> MethodTableLookup ---> _lookUpImpOrForward,最后的_lookUpImpOrForward對應(yīng)的是c語言里面的lookUpImpOrForward,

匯編c _lookUpImpOrForward ---> lookUpImpOrForward,去掉下劃線
c匯編 lookUpImpOrForward ---> _lookUpImpOrForward,加上下劃線

4. lookUpImpOrForward源碼

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    // 找不到imp執(zhí)行崩潰程序的指針
    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.
    checkIsKnownClass(cls); // 判斷類是否被認可
    // 是否實現(xiàn)和初始化,沒有的話,遞歸實現(xiàn)和初始化所有的類,包括isa指向鏈的類和superclass繼承鏈的類,用于下面的方法查找
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();

  // curClass臨時變量,cls未改變
    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().

    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES 
//iOS系統(tǒng)&&64位真機,再查找一次第一個節(jié)點的cache,
// 但是這次與_objc_msgSend查找流程是不一樣的,原理是一樣的,否則就會死循環(huán)了
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock; // 找到imp,goto 跳出for循環(huán)
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            // 二分法查找methodList
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }

            // curClass當(dāng)前類沒有找到curClass賦值為父類,同時判斷父類是不是nil,
            // 即根類NSObject已經(jīng)查找過了,還是沒有imp,則break結(jié)束for跳出for循環(huán)
           // 同時imp指向崩潰的指針地址
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) { //  NSObject ---> superclass == nil 跳出for循環(huán)
                // 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.
        // 父類繼承鏈中有循環(huán)則報錯
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        // 查找父類的cache,cache_getImp()的實現(xiàn)也是匯編語言,但是與objc_msgSend匯編語言查詢cache的流程是不一樣的
        // 原理是一樣的,有就返回imp,沒有則返回nil繼續(xù)后面的流程
        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.
    // forward_imp 報錯指針,報錯之前會執(zhí)行這里resolveMethod_locked 動態(tài)方法決議
    // behavior開關(guān),動態(tài)方法決議只會來一次,每個方法執(zhí)行一次,不是總共執(zhí)行一次
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER; //  異或 不同為1,相同為0
        return resolveMethod_locked(inst, sel, cls, behavior);// resolveInstanceMethod如果沒有動態(tài)添加imp會進來兩次也說明了每個方法都有一次動態(tài)方法決議的機會包括resolveInstanceMethod
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        // 找到imp,寫入superclass對應(yīng)的類的cache里面去
        // b[i].set<Atomic, Encoded>(b, sel, imp, cls());
        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;
}

lookUpImpOrForward()函數(shù)核心邏輯
1.整個查找的流程邏輯上的分析
2.methodList二分查找邏輯上的分析
3.getImp()objc_msgSend查找cache上的區(qū)別
4.找到imp,寫入到superclass對應(yīng)的類的cache里面去
5.查找到NSObjectcachemethodList,依然沒有找到imp,則此時imp = forward_imp,這是指向崩潰函數(shù)的指針,如果動態(tài)方法決議找到imp,則直接返回imp,如果找不到依然指向imp = forward_imp,但是此時是掛起的狀態(tài),允許iOS開發(fā)者實現(xiàn)后面的消息轉(zhuǎn)發(fā),如果轉(zhuǎn)發(fā)有效,快速轉(zhuǎn)發(fā)的話imp指向其他對象或者類方法的imp執(zhí)行其代碼,慢速轉(zhuǎn)發(fā)的話sel包裝成任務(wù)NSInvocation,即有認可iOS系統(tǒng)就不會崩潰了,也就是sel對應(yīng)的imp有了著落,此時sel不再指向forward_imp,也就不會崩潰了。

5.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(); // cls->data() -->rw -->methods()是一個二維數(shù)組
    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->isExpectedSize();
    
    if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
            return m;
    }

#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)
{
    if (list->isSmallList()) {
        if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
        } else {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
        }
    } else {
        return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
    }
}

5.findMethodInSortedMethodList源碼,二分查找主要在這里面,排序好的,地址由小到大

ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);

    auto first = list->begin(); //指針數(shù)組開始的指針
    auto base = first; 
    decltype(first) probe; //動態(tài)指針

    uintptr_t keyValue = (uintptr_t)key; // 目標(biāo)地址
    uint32_t count;
    
    // count >> 1 --> 二分
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1); 
        
        uintptr_t probeValue = (uintptr_t)getName(probe); // 動態(tài)指針地址
        
        if (keyValue == probeValue) { //  找到imp
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            //分類優(yōu)先,while多個分類,兩個相等說明分類和主類,分類和分類之間同名方法不會被覆蓋
            // 分類在前,分類優(yōu)先,所以,while循環(huán)查看前一個是否相等,即sel是否相同
            // 相同則優(yōu)先分類的方法
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) { 
                probe--; // 指針向前移動
            }
            return &*probe; // 返回method
        }
        
        if (keyValue > probeValue) { // 如果keyValue地址大于當(dāng)前的probeValue
            base = probe + 1;  // base后移,對后面的進行二分
            count--; // count--
        }
    }
    
    return nil;
}

二分查找詳細分析
假設(shè)總共8個method元素,sel對應(yīng)的method在下標(biāo)為0的位置上

value -- index 如下

0x10    0x20    0x30    0x40    0x50    0x60    0x70    0x80
0       1         2      3        4      5      6       7


first ---> 0
base ---> 0

for 循環(huán)第1次
count = 8, count != 0
probe ---> 4
兩個if都不滿足條件

for 循環(huán)第2次
count = 8, count != 0, (count >>= 1) = 4 ---> count = 4
probe = 0 + count >>= 1 
probe = 0 + 2
probe ---> 2
兩個if都不滿足條件

for 循環(huán)第4次
count = 4, count != 0, (count >>= 1) = 2 ---> count = 2
probe = 0 + count >>= 1 
probe = 0 + 1
probe ---> 1
兩個if都不滿足條件

for 循環(huán)第5次
count = 2, count != 0, (count >>= 1) = 1 ---> count = 1
probe = 0 + count >>= 1 
probe = 0 + 0
probe ---> 0
if (keyValue == probeValue) {} 條件滿足返回method

可見keyValue在第一次二分的后面位置的時候probe逐一后指,count是不斷二分的
probe ---> 4 ---> 2 ---> 1 ---> 0
count ---> 8 ---> 4 ---> 2 ---> 1

假設(shè)總共8個method元素,sel對應(yīng)的method在下標(biāo)為3的位置上

value -- index 如下

0x10    0x20    0x30    0x40    0x50    0x60    0x70    0x80
0       1         2      3        4      5      6       7


first ---> 0
base ---> 0

for 循環(huán)第1次
count = 8, count != 0
probe ---> 4
兩個if都不滿足條件

for 循環(huán)第2次
count = 8, count != 0, (count >>= 1) = 4 ---> count = 4
probe = 0 + count >>= 1 
probe = 0 + 2 = 2
probe ---> 2
if (keyValue > probeValue) {
    base = 2 + 1 = 3
    count = 3
}

for 循環(huán)第3次
count = 3, count != 0, (count >>= 1) = 1 ---> count = 1
probe = 3 + count >>= 1 
probe = 3 + 0
probe ---> 3
if (keyValue == probeValue) {} 條件滿足返回method

可見keyValue在第一次二分的后面位置的時候probe逐一后指,count是不斷二分的
probe ---> 4 ---> 2 ---> 3
count ---> 8 ---> 4 ---> 3 ---> 1

假設(shè)總共8個method元素,sel對應(yīng)的method在下標(biāo)為7的位置上

value -- index 如下

0x10    0x20    0x30    0x40    0x50    0x60    0x70    0x80
0       1         2      3        4      5      6       7


first ---> 0
base ---> 0

for 循環(huán)第1次
count = 8, count != 0
probe ---> 4
滿足  if (keyValue > probeValue) {
    base ---> 5
    count = 7
}

for 循環(huán)第2次
count = 7, count != 0, (count >>= 1) = 3 ---> count = 3
probe = 5 + count >>= 1 
probe = 5 + 1 
probe ---> 6
滿足  if (keyValue > probeValue) {
    base ---> 7
    count = 2
}

for 循環(huán)第3次
count = 2, count != 0, (count >>= 1) = 1 ---> count = 1
probe = 7 + count >>= 1 
probe = 7 + 0
probe ---> 7
if (keyValue == probeValue) {} 條件滿足返回method

可見keyValue在第一次二分的后面位置的時候probe逐一后指,count是不斷二分的
probe ---> 4 ---> 5 ---> 6 ---> 7
count ---> 8 ---> 3 ---> 1

6.cache_getImp()匯編源碼_cache_getImp和_objc_msgSend的區(qū)別

cache_getImp,匯編查找類的緩存,這個方法與objc_msgSend第一階段查找第一個節(jié)點cache,原理一樣,但是流程不一樣,_objc_msgSend ---> __objc_msgSend_uncached ---> MethodTableLookup ---> _lookUpImpOrForward
_cache_getImp ---> imp or nil

_cache_getImp匯編源碼
CacheLookup GETIMP, 參數(shù)為GETIMP,找到返回imp,找不到返回nil

    STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0, 0
    CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant

LGetImpMissDynamic: // 找不到返回nil
    mov p0, #0
    ret

LGetImpMissConstant: // 找不到返回nil
    mov p0, p2
    ret

    END_ENTRY _cache_getImp

_objc_msgSend 部分源碼
CacheLookup NORMAL,這個參數(shù)是NORMAL,即找不到跳轉(zhuǎn)到methodList查找流程里面來

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    cmp p0, #0          // nil check and tagged pointer check // p0和空對比 p0寄存器第一個位置存儲對象 判斷對象是否為空
#if SUPPORT_TAGGED_POINTERS // 支撐小對象
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero // 為空return
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

7.對象方法和類方法整體查找流程
LGTeacher繼承自LGPerson,LGPerson繼承自NSObject,下面的cache查找都是匯編語言實現(xiàn)的,目的是快速

對象方法查找流程,
teacher
---> cache(LGTeacher) ---> methodList(LGTeacher) ---> curClass = curClass->getSuperclass()&& != nil(curClass = LGPerson)
---> cache(LGPerson) ---> methodList(LGPerson) ---> curClass = curClass->getSuperclass()&& != nil(curClass = NSObject)
---> cache(NSObject) ---> methodList(NSObject) ---> curClass = curClass->getSuperclass() == nil
---> 跳出for循環(huán) ---> 動態(tài)方法決議 ---> 快速消息轉(zhuǎn)發(fā) ---> 慢速消息轉(zhuǎn)發(fā)

類方法查找流程
LGTeacher

---> cache(LGTeacher元類) ---> methodList(LGTeacher元類) ---> curClass = curClass->getSuperclass()&& != nil(LGPerson元類)
---> cache(LGPerson元類) ---> methodList(LGPerson元類) ---> curClass = curClass->getSuperclass()&& != nil(NSObject根元類)
---> cache(NSObject根元類) ---> methodList(NSObject根元類) ---> curClass = curClass->getSuperclass()&& != nil(NSObject根類)
---> cache(NSObject根類) ---> methodList(NSObject根類) ---> curClass = curClass->getSuperclass() == nil
---> 跳出for循環(huán) ---> 動態(tài)方法決議 ---> 快速消息轉(zhuǎn)發(fā) ---> 慢速消息轉(zhuǎn)發(fā)

8.log_and_fill_cache源碼 sel和imp寫入cache的流程

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
    cls->cache.insert(sel, imp, receiver);
}

insert 寫入部分源碼

void cache_t::insert(SEL sel, IMP imp, id receiver) {
    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m); //哈希算法函數(shù)利用地址唯一性大概率begin不會重復(fù)
    mask_t i = begin;

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot.
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set<Atomic, Encoded>(b, sel, imp, cls());  // cls() 寫入當(dāng)前cache對應(yīng)的類或者元類
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));
}

cache獲取class指針,前移16個字節(jié)

Class cache_t::cls() const
{
    // 當(dāng)前的cache 向前移動16字節(jié)指向其class
    return (Class)((uintptr_t)this - offsetof(objc_class, cache));
}

9.resolveMethod_locked動態(tài)方法決議調(diào)用函數(shù)源碼
這里面的核心思想就是在類的這兩個方法+ (BOOL)resolveInstanceMethod:(SEL)sel、+ (BOOL)resolveClassMethod:(SEL)sel里面,創(chuàng)建對應(yīng)selimp,分別動態(tài)添加對象方法類方法methodcache,添加到或者元類里面,然后再次執(zhí)行lookUpImpOrForwardTryCache這個函數(shù),查找邏輯上與lookUpImpOrForward是一樣的,只不過和lookUpImpOrForward是兩個入口,目的是如果找到則返回imp,找不到執(zhí)行后面的消息轉(zhuǎn)發(fā)流程,就是給你指定的方法去添加method,然后,再查一次,有imp返回method,沒有就返回nil,然后執(zhí)行后面的消息轉(zhuǎn)發(fā)流程

源碼里英文注釋1

// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);

源碼里英文注釋2

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

向resolveInstanceMethod發(fā)送消息核心代碼 ---> 用于添加對象方法

SEL resolve_sel = @selector(resolveInstanceMethod:); 
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; 
bool resolved = msg(cls, resolve_sel, sel); //resolveInstanceMethod:

向resolveInstanceMethod發(fā)送消息核心代碼 ---> 用于添加類方法

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

正常方法的objc_msgSend

LGPerson *person = [LGPerson alloc];   
objc_msgSend(person,sel_registerName("sayNB"));
[person sayNB];

很顯然上面的源碼里向resolveInstanceMethodresolveClassMethod發(fā)送消息的代碼,與我們正常調(diào)用方法對應(yīng)的objc_msgSend是不一樣的,從調(diào)用上、邏輯上、源碼注釋上都有體現(xiàn),邏輯上如果與普通的objc_msgSend一樣的話就有可能存在循環(huán)而且也沒意義,已經(jīng)找過一次了,是動態(tài)添加一個,后面的代碼去查詢,然后返回imp,走后面的消息轉(zhuǎn)發(fā)流程,調(diào)用上兩種方式是不一樣的,注釋上見上面的英文源碼注釋

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

    runtimeLock.unlock();
    // 動態(tài)方法決議 :給一次機會 重新查詢 在resolveInstanceMethod里添加sel對應(yīng)的imp,即添加method到cache,對應(yīng)的實現(xiàn)是添加imp,不是光寫了這個方法
    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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // 蘋果源碼英文注釋,驗證了上面的結(jié)論
    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

resolveInstanceMethod源碼

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:); //發(fā)送消息的sel

    // lookup resolve_sel ---> resolveInstanceMethod是否有這個方法,有這個方法調(diào)用才有意義,否則就崩潰了
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend; //發(fā)送消息 //中間層攔截
    bool resolved = msg(cls, resolve_sel, sel); //resolveInstanceMethod:中間層使得imp有值,下次查找就有了imp
    // 在這個resolve_sel里面,給cls這個類添加一個sel為sel的method,用于返回當(dāng)前sel缺少的method
    // 緩存這個返回的method,不管結(jié)果好壞,即有沒有實現(xiàn),但是resolveInstanceMethod:這個方法不負責(zé)啟動再次查詢sel對應(yīng)的method是否已經(jīng)實現(xiàn)并且緩存在類方法列表或者cache里面
    // 所以,需要下面的再次查詢
    // Cache the result (good or bad) so the resolver doesn't fire next time. //
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNilTryCache(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));
        }
    }
}

resolveClassMethod 源碼

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNilTryCache(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 resolveClassMethod:%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));
        }
    }

2.代碼驗證動態(tài)方法決議和消息轉(zhuǎn)發(fā)

消息轉(zhuǎn)發(fā)分為快速轉(zhuǎn)發(fā)和慢速轉(zhuǎn)發(fā),動態(tài)方法決議和消息轉(zhuǎn)發(fā)可以在各自的類里面實現(xiàn),但是實際工程里面各個類都這么實現(xiàn)的不太現(xiàn)實,基本上失去了意義,好的方式是在NSObject分類里面實現(xiàn)上面的流程

1.LGPerson代碼

@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayPersonClass;
@end

2.調(diào)用

self.person = [[LGPerson alloc] init];
[self.person sayHello];
[LGPerson sayPersonClass];

3.NSObject 分類 NSObject+Resolve中的動態(tài)方法決議

+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(sayPersonClass)) { // 判斷sel
        NSLog(@"resolveClassMethod == %@", NSStringFromSelector(sel));
        Class metaClass = objc_getMetaClass(class_getName([self class]));
        class_addMethod(metaClass, sel, (IMP)classFunction,"v@:");
        return YES;
    }
    return NO;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(sayHello)) { // 判斷sel
        NSLog(@"resolveInstanceMethod == %@", NSStringFromSelector(sel));
        class_addMethod(self, sel, (IMP)instanceFunction, "v@:");
        return YES;
    }
    return NO;
}

int classFunction(id target, SEL cmd, ...) {
    return 0;
}

int instanceFunction(id target, SEL cmd, ...) {
    return 0;
}

Xcode 控制臺輸出

2022-06-05 09:36:36.696049+0800 方法流程001[2451:105070] resolveInstanceMethod == sayHello
2022-06-05 09:36:36.696129+0800 方法流程001[2451:105070] resolveClassMethod == sayPersonClass

4.動態(tài)方法決議小結(jié)
需要判斷是哪個sel沒有實現(xiàn)你讓動態(tài)添加對應(yīng)的sel,那么我都知道哪個沒有實現(xiàn)的話根本不需要走到動態(tài)方法決議,直接在對應(yīng)的類中添加對應(yīng)的方法就可以了啊

如果動態(tài)方法決議沒有實現(xiàn),下一步會執(zhí)行到消息轉(zhuǎn)發(fā),消息轉(zhuǎn)發(fā)分快速消息轉(zhuǎn)發(fā)和慢速消息轉(zhuǎn)發(fā)

5.Teacher類代碼

@interface Teacher : NSObject
- (void)sayHello;
+ (void)sayPersonClass;
@end

@implementation Teacher
- (void)sayHello {
    NSLog(@"sayHello in Teacher");
}

+ (void)sayPersonClass {
    NSLog(@"sayPersonClass in Teacher");
}
@end

6.NSObject 分類 NSObject+Resolve中的快速消息轉(zhuǎn)發(fā)

//1.對象方法快速轉(zhuǎn)發(fā)
- (id)forwardingTargetForSelector:(SEL)aSelector {
    // 對應(yīng)的類里面必須有aSelector對應(yīng)的方法才可以,如果知道要崩潰
    if (aSelector == @selector(sayHello)) {
        NSLog(@"- forwardingTargetForSelector == %@", NSStringFromSelector(aSelector));

        return [[Teacher alloc] init];;
    }
    
    return nil;
}

//1.類方法快速轉(zhuǎn)發(fā),頭文件里并沒有這個方法
+ (id)forwardingTargetForSelector:(SEL)aSelector {
    // 對應(yīng)的類里面必須有aSelector對應(yīng)的方法才可以,如果知道要崩潰
    if (aSelector == @selector(sayPersonClass)) {
        NSLog(@"+ forwardingTargetForSelector == %@", NSStringFromSelector(aSelector));
        return [Teacher class];
    }
    
    return nil;
}

Xcode 控制臺輸出

2022-06-05 09:58:39.096094+0800 方法流程001[2686:121141] - forwardingTargetForSelector == sayHello
2022-06-05 09:58:39.096145+0800 方法流程001[2686:121141] sayHello in Teacher
2022-06-05 09:58:39.096184+0800 方法流程001[2686:121141] + forwardingTargetForSelector == sayPersonClass
2022-06-05 09:58:39.096213+0800 方法流程001[2686:121141] sayPersonClass in Teacher

7.快速消息轉(zhuǎn)發(fā)小結(jié)
需要判斷是哪個sel沒有實現(xiàn)然后需要轉(zhuǎn)發(fā)到其他類并且該類內(nèi)部必須實現(xiàn)上面對應(yīng)的方法,那么我都知道哪個沒有實現(xiàn)的話根本不需要走到快速消息轉(zhuǎn)發(fā)的流程,直接在對應(yīng)的類中添加對應(yīng)的方法就可以了,消息快速轉(zhuǎn)發(fā)即轉(zhuǎn)發(fā)給別的去處理,并且這個必須實現(xiàn)sel對應(yīng)的方法

8.NSObject 分類 NSObject+Resolve中的慢速消息轉(zhuǎn)發(fā)
methodSignatureForSelector和forwardInvocation組合使用

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"methodSignatureForSelector == %s", aSelector);
    NSMethodSignature * signature = [NSMethodSignature instanceMethodSignatureForSelector:@selector(showInstanceMessage:)];
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"forwardInvocation anInvocation target == %@", [anInvocation target]);
    NSLog(@"forwardInvocation anInvocation selector == %@", NSStringFromSelector(anInvocation.selector));
}

- (void)showInstanceMessage:(NSString *)crashMessage {
    NSLog(@"showInstanceMessage == %@", crashMessage);
}

Xcode 控制臺輸出

2022-06-05 10:56:34.255052+0800 方法流程001[3140:158691] methodSignatureForSelector == sayHello
2022-06-05 10:56:34.255121+0800 方法流程001[3140:158691] forwardInvocation anInvocation target == <LGPerson: 0x600002034430>
2022-06-05 10:56:34.255157+0800 方法流程001[3140:158691] forwardInvocation anInvocation selector == sayHello

8.慢速消息轉(zhuǎn)發(fā)小結(jié)
慢速消息只有轉(zhuǎn)發(fā)對象方法的方法,目前沒發(fā)現(xiàn)類方法的處理。慢速消息轉(zhuǎn)發(fā)對象方法非常友好,只要實現(xiàn)了上面兩個方法methodSignatureForSelector和forwardInvocation其他的不用做什么,就能避免對象方法找不到imp的這種崩潰

類方法在快速消息轉(zhuǎn)發(fā)forwardingTargetForSelector,頭文件里面沒有對應(yīng)的類方法,而且對象和類方法的處理都不是很友好,那么類方法怎么統(tǒng)一處理好呢,+ (BOOL)resolveClassMethod:(SEL)sel 動態(tài)方法決議里面有非常明確的針對類方法的處理函數(shù),是否可以改進一下呢,因為如果知道哪個sel沒有實現(xiàn),直接寫出來到對應(yīng)的類里面就可以了,所以,無需判斷sel才是統(tǒng)一的好的處理方式

9.NSObject 分類 NSObject+Resolve中的快速消息轉(zhuǎn)發(fā)

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"resolveClassMethod == %@", NSStringFromSelector(sel));
    Class metaClass = objc_getMetaClass(class_getName([self class]));
    class_addMethod(metaClass, sel, (IMP)classFunction,"v@:");
    return NO;
}

Xcode 控制臺輸出

2022-06-05 11:15:50.602958+0800 方法流程001[3216:169916] resolveClassMethod == supportsSecureCoding
2022-06-05 11:15:50.613765+0800 方法流程001[3216:169724] resolveClassMethod == bundleForClass
2022-06-05 11:15:50.617928+0800 方法流程001[3216:169724] methodSignatureForSelector == sayHello
2022-06-05 11:15:50.617990+0800 方法流程001[3216:169724] forwardInvocation anInvocation target == <LGPerson: 0x600003c7c2a0>
2022-06-05 11:15:50.618028+0800 方法流程001[3216:169724] forwardInvocation anInvocation selector == sayHello
2022-06-05 11:15:50.618060+0800 方法流程001[3216:169724] resolveClassMethod == sayPersonClass

可見resolveClassMethod:類方法的處理,不進行sel判斷會有一些系統(tǒng)的類方法進入,但是不會崩潰,與對象方法的慢速消息轉(zhuǎn)發(fā)結(jié)合到一起可以沒有針對性得處理對象方法和類方法找不到imp這樣的崩潰,比較完美。

3.統(tǒng)一實現(xiàn)避免類方法和對象方法的崩潰 ---> unrecognized selector sent to class

NSObject+resolve 分類
對象方法沒有實現(xiàn)避免崩潰:methodSignatureForSelector:和forwardInvocation:
類方法沒有實現(xiàn)避免崩潰:resolveClassMethod:

?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

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