Runtime源碼解析-消息發送

Runtime源碼解析-消息發送

  • 在我們平時開發項目中,除了頻繁的創建對象之外,用的最多的就是調用方法。本篇文章就是主要研究方法是如何調用的。

前言

  • Objective-C中的“方法調用”其實應該叫做消息傳遞
    • 我們為什么需要消息傳遞?
    • 在很多語言,比如 C ,調用一個方法其實就是跳到內存中的某一點并開始執行一段代碼。沒有任何動態的特性,因為這在編譯時就決定好了。而在 Objective-C 中,[object foo] 語法并不會立即執行 foo這個方法的代碼。它是在運行時給object 發送一條叫 foo 的消息。這個消息,也許會由 object來處理,也許會被轉發給另一個對象,或者不予理睬假裝沒收到這個消息。多條不同的消息也可以對應同一個方法實現,這些都是在程序運行的時候決定的。
  • 在底層中,[receiver message]會被翻譯為 objc_msgSend(receiver, @selector(message))。也就是通過objc_msgSend()方法進行調用。

objc_msgSend

  • libobjc中,該方法是通過匯編實現的,我們可以在objc-msg-arm64.s找到對應的實現。
    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    # 1. 判斷receiver是否為nil
    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    # 2. 獲取isa和對應class
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class
LGetIsaDone:
    # 3. 去緩存中查找方法
    // calls imp or objc_msgSend_uncached
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // nil check
    GetTaggedClass
    b   LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    ret

    END_ENTRY _objc_msgSend
  • 該方法中主要有以下幾個步驟
    1. 首先判斷消息接收者是否為空,如果為空的話,則跳轉至LReturnZero方法,直接退出該方法
    2. 去獲取該對象的isa,并且通過isa獲取對應的class
    3. 去緩存中查找方法

CacheLookup 緩存查找

.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
    mov x15, x16            // stash the original isa
LLookupStart\Function:
    // p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    ldr p10, [x16, #CACHE]              // p10 = mask|buckets
    lsr p11, p10, #48           // p11 = mask
    and p10, p10, #0xffffffffffff   // p10 = buckets
    and w12, w1, w11            // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 // arm64系統
    # 1. 把isa地址移動16個字節,找到其中cache_t地址
    ldr p11, [x16, #CACHE]          // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
    tbnz    p11, #0, LLookupPreopt\Function
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
#else
    # 2. 獲取buckets的首地址
    # p10 = _bucketsAndMaybeMask & 0x0000fffffffffffe
    and p10, p11, #0x0000fffffffffffe   // p10 = buckets
    tbnz    p11, #0, LLookupPreopt\Function
#endif
    # 3. 獲取傳入_cmd的哈希值
    eor p12, p1, p1, LSR #7
    and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
    and p10, p11, #~0xf         // p10 = buckets
    and p11, p11, #0xf          // p11 = maskShift
    mov p12, #0xffff
    lsr p11, p12, p11           // p11 = mask = 0xffff >> p11
    and p12, p1, p11            // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
    
    # 4. 去緩存中去查找方法
    add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
                        
                        // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel != _cmd) {
    b.ne    3f              //         scan more
                        //     } else {
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b


#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    add p13, p10, w11, UXTW #(1+PTRSHIFT)
                        // p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                        // p13 = buckets + (mask << 1+PTRSHIFT)
                        // see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p13, p10, p11, LSL #(1+PTRSHIFT)
                        // p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                        // p12 = first probed bucket

                        // do {
4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel == _cmd)
    b.eq    2b              //         goto hit
    cmp p9, #0              // } while (sel != 0 &&
    ccmp    p13, p12, #0, ne        //     bucket > first_probed)
    b.hi    4b

# 省略部分實現

.endmacro
  • 該方法內部主要做了一下幾件事
    1. 通過isa指針,獲取cache_t結構的地址
    2. 通過cache_t的地址,獲取buckets的首地址
    3. 計算傳入_cmd的哈希值,用作查詢起始位置
    4. 循環遍歷buckets,找到對應方法實現
  • 這里我們主要討論arm64下的實現,也就是條件判斷中CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16

1. 獲取cache_t

ldr p11, [x16, #CACHE]          // p11 = mask|buckets
  • objc_class結構中,cache_t相對首地址偏移16個字節
  • 這里x16存儲了isa地址,然后偏移16個字節,把得到的cache_t地址存儲在p11寄存器中

2. 獲取buckets

#if CONFIG_USE_PREOPT_CACHES
    #if __has_feature(ptrauth_calls) 
        tbnz    p11, #0, LLookupPreopt\Function
        and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    #else // 真機,走下面
        and p10, p11, #0x0000fffffffffffe   // p10 = buckets
        tbnz    p11, #0, LLookupPreopt\Function
    #endif
    eor p12, p1, p1, LSR #7
    and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
  • p11 = cache_t的地址cache_t內部第一個元素是_bucketsAndMaybeMask。然后把cacht_t(_bucketsAndMaybeMask) & 0x0000fffffffffffe,就得到了buckets的首地址。

3. 計算_cmd哈希值

eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48      // x12 = (_cmd ^ (_cmd >> 7)) & mask
  • 其中eor是異或,p1 = _cmdp12 = p1^(p1 >> 7) = _cmd^(_cmd >> 7)。這是我們在存儲方法時,采用的encode方法。具體實現可以查看前面提到cache文章。這樣就得到_cmd編碼后的值
  • LSR表示邏輯向右偏移,p11, LSR #48_bucketsAndMaybeMask偏移48位,拿到前16位,得到mask的值
  • and表示與,把編碼后的_cmd & mask,就得到了需要查詢方法對應的哈希值。

4. 遍歷查詢

  • 接著就可以進行查詢過程
add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

# 流程1
                        // do {
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel != _cmd) {
    b.ne    3f              //         scan more
                        //     } else {
# 流程2
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
# 流程3
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
    cmp p13, p10            // } while (bucket >= buckets)
    b.hs    1b


# 向前查詢到首位置,沒有找到合適的方法。跳轉至最后一個元素,接著查詢
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    add p13, p10, w11, UXTW #(1+PTRSHIFT)
                        // p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    // mask = capacity - 1,開辟內存容量-1.找到最后一個bucket的位置
    add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
                        // p13 = buckets + (mask << 1+PTRSHIFT)
                        // see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p13, p10, p11, LSL #(1+PTRSHIFT)
                        // p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                        // p12 = first probed bucket

# 流程4
                        // do {
4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
    cmp p9, p1              //     if (sel == _cmd)
    b.eq    2b              //         goto hit
    cmp p9, #0              // } while (sel != 0 &&
    ccmp    p13, p12, #0, ne        //     bucket > first_probed)
    b.hi    4b

# 沒有找到
LLookupEnd\Function:
LLookupRecover\Function:
    b   \MissLabelDynamic
  1. 通過下標index獲取對應的bucket。p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

  2. 獲取對應的bucket,然后取出impsel分別存放到p17p9中,并且bucket--。原因是方法緩存在存儲的時候,采用的是向前插入。

    1. 流程1:比較當前bucketsel和傳入的_cmd是否相同,
      1. 如果不同則跳轉至流程3
      2. 如果相同則走流程2,CacheHit
    2. 流程2: CacheHit:表示緩存命中,直接返回該方法
    3. 流程3:
      1. 首先判斷當前sel是否為nil,如果為空,說明沒有找到緩存方法,跳轉至MissLabelDynamic,走__objc_msgSend_uncached流程。下面篇章具體講解。
      2. 如果當前sel不是我們需要找的,則去找下一個,直到找到第一個存儲位置。如果循環到第一個bucket都沒有找到對應的方法。則跳轉至最后一個元素接著向前查找
    4. 流程4:從最后一個元素,開始向前查找
      1. 如果找到了,就跳轉至流程2
      2. 如果沒有找到,則向前查找,直到查詢到我們首次哈希計算的下標值。
  3. 一直沒有找到,最后走到MissLabelDynamic,也就是__objc_msgSend_uncached流程。

__objc_msgSend_uncached

  • 在上面我們會去類中的緩存查找方法的實現,如果該方法沒有添加到緩存中就會調用__objc_msgSend_uncached方法。我們繼續查看一下,沒有命中緩存是如何查詢方法的
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search

MethodTableLookup
TailCallFunctionPointer x17

END_ENTRY __objc_msgSend_uncached
  • 里面就只有兩個方法,一個是MethodTableLookup,另一個是TailCallFunctionPointer。我們先進入TailCallFunctionPointer實現。
.macro TailCallFunctionPointer
    // $0 = function pointer value
    braaz   $0
.endmacro
  • 發現它僅僅是指返回傳入的地址,并跳轉過去。并沒有做查詢操作,所以這并不是我們需要關心的方法。那我們就查看一下MethodTableLookup的實現
.macro MethodTableLookup
    
    SAVE_REGS MSGSEND

    // 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_REGS MSGSEND

.endmacro
  • 發現該方法中,通過_lookUpImpOrForward來查詢到具體imp。我們接著全局搜索發現沒有它的定義,這個時候我們去掉下劃線,就發現了結果。由于匯編函數會比c++函數多一個下劃線。

lookUpImpOrForward

NEVER_INLINE
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 (slowpath(!cls->isInitialized())) {...}


    runtimeLock.lock();

    // 1. 是否把類注冊到內存中
    checkIsKnownClass(cls);
    // 2. 初始化當前類和父類
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;
    
    // 3. 開始查詢方法。需要先再次查找緩存,如果沒找到在開始去類中查詢
    for (unsigned attempts = unreasonableClassCount();;) {
        // 判斷緩存中是否存在
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // 在當前類中查找方法
            method_t *meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }
            
            // curClass = curClass->getSuperclass() 直到為nil走if里面的流程,不為nil走下面流程
            if (slowpath((curClass = curClass->getSuperclass()) == 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.");
        }

        // Superclass cache.
        // 去父類的緩存中查找
        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.
    // 4. 如果沒有找到實現,調用resolveMethod_locked來去實現
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 // 5. 方法找到
 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        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;
}
  • 該方法主要做了以下幾件事
    1. 判斷類是否加載到內存中
    2. 對應的類和元類、以及對應的父類是否初始化完畢,為接下來有可能到父類中查詢做準備
    3. 開始查詢方法。需要先查找緩存,如果沒找到再開始去類中查詢,如果當前類沒有,則去它的父類去查詢,如果找到方法了,則跳轉至done
    4. 如果沒有找到
      1. 首次的話,系統會調用resolveMethod_locked給你一次機會,判斷是否有動態方法決議。
      2. 非首次的話,則直接返回forward_imp
    5. 方法找到
1. 判斷類是否加載到內存中
ALWAYS_INLINE
static void
checkIsKnownClass(Class cls)
{
    if (slowpath(!isKnownClass(cls))) {
        _objc_fatal("Attempt to use unknown class %p.", cls);
    }
}
  • 通過isKnownClass來判斷
ALWAYS_INLINE
static bool
isKnownClass(Class cls)
{
    if (fastpath(objc::dataSegmentsRanges.contains(cls->data()->witness, (uintptr_t)cls))) {
        return true;
    }
    auto &set = objc::allocatedClasses.get();
    return set.find(cls) != set.end() || dataSegmentsContain(cls);
}
  • 通過全局allocatedClasses表中去判斷,是否已經加載到內存中
2. 初始化對應類和元類
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
    runtimeLock.assertLocked();
    // 判斷類是否已經實現
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
    // 判斷類是否初始化
    if (slowpath(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
    }
    return cls;
}
  • 通過realizeClassMaybeSwiftAndLeaveLocked去實現類,主要是按照isa和繼承鏈去實現bits中的data數據。
  • 通過initializeAndLeaveLocked去初始化類
3. 查詢方法
for (unsigned attempts = unreasonableClassCount();;) {
    // 如果存在共享緩存,則先去緩存中查找
    if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
        #if CONFIG_USE_PREOPT_CACHES
        imp = cache_getImp(curClass, sel);
        if (imp) goto done_unlock;
        curClass = curClass->cache.preoptFallbackClass();
        #endif
    } else {
        // curClass method list.
        method_t *meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp(false);
            goto done;
        }

        if (slowpath((curClass = curClass->getSuperclass()) == 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.");
    }

    // Superclass cache.
    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;
    }
}
  1. 先判斷是否有共享緩存,如果存在先去緩存中查找,然后再去列表中查詢
// curClass method list.
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
    imp = meth->imp(false);
    goto done;
}

if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
    // No implementation found, and method resolver didn't help.
    // Use forwarding.
    imp = forward_imp;
    break;
}
  1. 先在當前類中去查詢,如果查詢到了,則直接跳轉至done。如果沒有查到,則設置curClass = curClass->getSuperclass()進入到父類中。我們看一下getMethodNoSuper_nolock是如何查詢的
getMethodNoSuper_nolock
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());

    auto const methods = cls->data()->methods();
    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;
}
  • 通過cls去獲取methods列表。這個列表是一個二維數組,如果對這一塊不夠了解,可以查看這篇文章Runtime源碼解析-類中bits。然后我們去遍歷這個二維數組,通過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;
    }

    return nil;
}
  • 一般情況下,都會走findMethodInSortedMethodList方法
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
    ASSERT(list);
    
    // 第一個方法位置
    auto first = list->begin();
    auto base = first;
    decltype(first) probe;

    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    // count = 數組的個數
    // count >>= 1 等價于 count = count / 2;
    for (count = list->count; count != 0; count >>= 1) {
        // 獲取當前偏移值
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)getName(probe);
        
        // 如果相等,匹配成功
        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)getName((probe - 1))) {
                probe--;
            }
            return &*probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}
  • 通過二分查找的方式,來查詢方法列表中的方法。如果沒有找到這返回nil
  1. 如果當前類沒有找到,則把curClass = curClass->getSuperclass()更新為當前類的父類
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
    _objc_fatal("Memory corruption in class list.");
}

// 先去父類的緩存中查找
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;
}
  • 先去父類的緩存中查找,如果沒有找到,就進入下一次循環。去父類的方法列表中去查找
  • 如果在緩存中找到,則跳轉至done
4. 方法未找到
  • 如果上面的查找流程未能找到,則說明當前類和父類確實沒有該方法,則進入動態決議過程
if (slowpath(behavior & LOOKUP_RESOLVER)) {
    behavior ^= LOOKUP_RESOLVER;
    return resolveMethod_locked(inst, sel, cls, behavior);
}
  • 這方法我們再下一節具體講解
5. 方法找到
 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
  • 如果方法找到了,我們會調用log_and_fill_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);
}
  • 該方法內部調用了cls->cache.insert(sel, imp, receiver);,這就我們在講類的緩存時,著重講解了如何插入的。這里我們也就明白了,方法是合適插入到緩存中的。

resolveMethod_locked

  • 我們在上面lookUpImpOrForward方法中,去類的方法列表,以及它對應的父類直達根類去查找對應方法的實現。如果找到了就直接返回,找不到的話就會進入到resolveMethod_locked流程。
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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    // 返回前面通過resolveInstanceMethod或resolveClassMethod添加的sel對應方法
    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:);

    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }
    // 調用resolveInstanceMethod方法
    // 通過 objc_msgSend 發送消息 接收者是cls
    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
    // 去查找sel對應的實現
    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));
        }
    }
}
  • 首先創建一個方法名為resolveInstanceMethodSEL resolve_sel
  • 直接調用objc_msgSend給類發送消息,從這里可以看出resolveInstanceMethod是類方法
  • 然后通過lookUpImpOrNilTryCache去緩存和方法列表中去查看,是否實現了resolveInstanceMethod該方法。
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}

static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();

    if (slowpath(!cls->isInitialized())) {
        // see comment in lookUpImpOrForward
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    // 在緩存中查找sel對應imp
    IMP imp = cache_getImp(cls, sel);
    if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    // 緩存沒有查詢到imp,進入方法列表中查詢
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
}
  1. 先在緩存中查找,如果imp存在則直接跳轉done流程
  2. 沒有查找到imp,進入方法列表進行查詢。注意,此次再進入方法列表查詢,behavior= 44 & 2= 0,不會再次進入動態方法決議
  3. done流程,如果 imp = _objc_msgForward_impcache,說明緩存中的是forward_imp,就直接返回nil,否者返回的imp

類方法動態決議

  • 如果是類方法,則調用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));
        }
    }
}
  • 該流程與resolveInstanceMethod方法類似。
  • 直接調用objc_msgSend給元類發送消息,可能resolveClassMethod實現了sel對應的imp
  • 然后通過lookUpImpOrNilTryCache去緩存和方法列表中去查看,是否實現了resolveClassMethod該方法。

消息轉發

  • 如果沒有實現動態決議,這個時候消息會進入轉發流程。分為快速轉發慢速轉發

快速轉發

  • 快速轉發是通過forwardingTargetForSelector實現,簡單理解指定一個對象,讓這個對象去接收消息。
  • 首先定義一個TestMain類和Test類,然后在main函數中調用test方法。TestMain沒有實現test方法,Test類實現test方法,TestMain類和Test類可以不是繼承關系
- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        return [[Test alloc] init];
    }
    return nil;
}
  • 這樣就可以讓你所指定的類去實現這個方法,如果沒有重寫該方法,說明沒有指定的類可以去實現該方法,則快速轉發也不行了。就進入到慢速轉發流程

慢速轉發

  • 慢速轉發是通過methodSignatureForSelectorforwardInvocation配合實現的。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[[Test alloc] init]];
}
  • 如果methodSignatureForSelector返回nil,慢速轉發時,程序會直接崩潰。

總結

  • 快速轉發:通過forwardingTargetForSelector實現,如果此時有指定的對象去接收這個消息,就會走之指定對象的查找流程,如果返回是nil,進入慢速轉發流程

  • 慢速轉發:通過methodSignatureForSelectorforwardInvocation共同實現,如果methodSignatureForSelector返回值是nil,慢速查找流程結束,如果有返回值forwardInvocation的事務處理不處理都不會崩潰。

總結

  • 消息發送流程總結,總共經歷了三個階段:
  1. 消息發送階段:負責從類及父類的緩存列表及方法列表查找方法。
  2. 動態解析階段:如果消息發送階段沒有找到方法,則會進入動態解析階段,負責動態的添加方法實現。
  3. 消息轉發階段:如果也沒有實現動態解析方法,則會進行消息轉發階段,將消息轉發給可以處理消息的接受者來處理。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容