IOS底層(十三): 消息流程(一)快速查找

OC底層源碼/原理合集

消息傳遞轉(zhuǎn)發(fā)首先肯定要了解個(gè)知識(shí)點(diǎn) runtime

官方文檔 Objective-C Runtime Programming Guide

runtime官方文檔

Runtime

runtime簡(jiǎn)稱運(yùn)行時(shí), 是Objective-C語(yǔ)言中非常重要的概念, 它不僅使得OC語(yǔ)言正常運(yùn)行, 還使得OC語(yǔ)言具有動(dòng)態(tài)的特性。Runtime的運(yùn)行機(jī)制使得OC能夠在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建類和對(duì)象, 進(jìn)行消息傳遞轉(zhuǎn)發(fā)等

運(yùn)行時(shí), 編譯時(shí)區(qū)別
  • 編譯時(shí): 其實(shí)就是正在編譯的時(shí)候, 編譯器幫你把源代碼翻譯成機(jī)器代碼的過(guò)程。 主要是對(duì)語(yǔ)言進(jìn)行最基本的檢查報(bào)錯(cuò)(詞法分析, 語(yǔ)法分析等)。例如: 編譯代碼時(shí)候如果有error, warning信息, 都是編譯器檢查出來(lái)的, 這種錯(cuò)誤叫編譯時(shí)錯(cuò)誤, 這個(gè)過(guò)程叫編譯時(shí)類型檢查 或者靜態(tài)類型檢查

    留意: 編譯時(shí)只是把代碼當(dāng)做文本掃描下, 并未分配內(nèi)存運(yùn)行

  • 運(yùn)行時(shí): 是代碼跑起來(lái), 被裝載到內(nèi)存中的過(guò)程(內(nèi)存中判斷), 是一個(gè)動(dòng)態(tài)階段。

例子:
運(yùn)行時(shí)/編譯時(shí)例子

沒(méi)有sayHello實(shí)現(xiàn)方法, 編譯時(shí)并無(wú)報(bào)錯(cuò), 而運(yùn)行時(shí)

運(yùn)行時(shí)/編譯時(shí)例子

可看到由于找不到方法而報(bào)錯(cuò), 這個(gè)例子也可以看出運(yùn)行與編譯時(shí)的區(qū)別



Runtime調(diào)用三種途徑

runtime調(diào)用三種途徑
  • 通過(guò)OC代碼,例如: [test sayNB] (Objective-C code)

  • 通過(guò)NSObject方法,例如: isKindOfClass, isMemberOf(Framework & Service 接口引入)

  • 通過(guò)Runtime API,例如: class_getInstanceSize (Runtime API)

compiler就是我們了解的編譯器, 那一層為編譯層即LLVM

runtime system libarary 就是底層庫(kù)



方法本質(zhì)

為了查看一下消息流程本質(zhì), 我們Clang一下

clang -rewrite-objc main.m -o main.cpp
cpp代碼

其實(shí)我們之前也看到過(guò), 方法的本質(zhì)就是objc_msgSend消息發(fā)送

驗(yàn)證一下,通過(guò) objc_msgSend方法來(lái)完成[person sayNB]的調(diào)用,查看其打印是否是一致

實(shí)現(xiàn)準(zhǔn)備:

1、調(diào)用objc_msgSend,需要導(dǎo)入頭文件#import <objc/message.h>
2、需要將 target → Build Setting → msg → enable strict checking of obc_msgSend callsYES 改為NO,將嚴(yán)厲的檢查機(jī)制關(guān)掉,否則objc_msgSend的參數(shù)會(huì)報(bào)錯(cuò)

關(guān)閉 enable strict checking of obc_msgSend calls
TestObj *test = [TestObj alloc];
[test sayNB];
objc_msgSend(test, sel_registerName("sayNB"));
// 方法在底層是消息
// 消息: 1.消息接受者 2. 消息主體

這里通過(guò) objc_msgSend調(diào)用方法

image.png

enable strict checking of obc_msgSend calls沒(méi)改的話會(huì)報(bào)這個(gè)錯(cuò)誤

C003A429-F6F5-43C7-9FC8-DF606E8C78B3.png

可看到兩者一致, 即[test sayNB]等同于objc_msgSend(test, sel_registerName("sayNB"));



調(diào)用父類方法

我們還可以模擬下調(diào)用父類方法, 具體通過(guò)objc_msgSendSuper實(shí)現(xiàn)

父類SAPerson定義方法sayHello, 子類 SATeacher繼承SAPerson, 不設(shè)置任何方法

調(diào)用父類方法

objc_msgSendSuper先看下底層

OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#endif

方法中有兩個(gè)參數(shù)(結(jié)構(gòu)體struct objc_super,方法名sel),其中sel方法名之前講過(guò), 而結(jié)構(gòu)體類型objc_super看下源碼

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};
#endif

objc_super結(jié)構(gòu)體可看出,需要指定接收者receiver 和父類 super_class兩個(gè)屬性

上面例子我們也可以看出, 子類調(diào)用父類方法[teacher sayHello]objc_msgSendSuper(&s, sel_registerName("sayHello"));都執(zhí)行了父類的中sayHello方法,我們有些疑問(wèn) 是不是方法調(diào)用,首先是在類中查找,如果類中沒(méi)有找到,會(huì)到類的父類中查找?。怎么通過(guò)方法名Sel 找到 接下來(lái)我們探索下這個(gè)問(wèn)題



objc_msgSend 快速查找流程分析

objc4源碼中,搜索objc_msgSend,這里留意下objc_msgSend是匯編寫的, 而不是C/C++寫的。首先所有代碼都會(huì)被翻譯成底層匯編, 然后通過(guò)編譯時(shí)翻譯成機(jī)器識(shí)別語(yǔ)言。

匯編語(yǔ)言有個(gè)特性:

  • 。舉個(gè)例子: 如果App 1s內(nèi)有成千上萬(wàn)個(gè)方法調(diào)用, 匯編語(yǔ)言比用C/C++寫少耗時(shí)0.01s, 也是會(huì)省下很多時(shí)間。

  • 動(dòng)態(tài)性(不確定性): C/C++相對(duì)于匯編來(lái)說(shuō)構(gòu)造更加靜態(tài), 可能不滿足消息發(fā)送很多情況

其實(shí)如果用C/C++寫消息發(fā)送其實(shí)也是可以的, 不過(guò)相對(duì)麻煩, 浪費(fèi)性能, 所以消息傳遞這一層面蘋果使用匯編寫的

objc_msgSend有一個(gè)消息接受者, 消息接受者才能找到你真正的尋根路徑。由于方法存在于類/元類里面, 方法查找需要ISA, ISA又存在對(duì)象里面無(wú)論是實(shí)例對(duì)象還是類對(duì)象。即有: 對(duì)象ISA方法(類) → cache(是否有緩存) → methodlist(無(wú)緩存, 存在bits中)

由于我們?nèi)粘i_(kāi)發(fā)的都是架構(gòu)是arm64(手機(jī)端),所以需要在arm64.s后綴的文件中查找objc_msgSend源碼實(shí)現(xiàn)

objc-msg-arm64s
// ---- 消息轉(zhuǎn)發(fā) ----  objc_msgSend主要是拿到接收者的isa信息

//   _objc_msgSend 的 匯編入口  
    ENTRY _objc_msgSend
//  無(wú)窗口
    UNWIND _objc_msgSend, NoFrame

//  p0其實(shí)是objc_msgSend的第一個(gè)參數(shù)-消息接收者receiver
    cmp p0, #0          // nil check and tagged pointer check

//  判空操作, p0與空作對(duì)比, 判斷接受者是否存在
//  是否支持taggedpointer(小對(duì)象類型)的流程
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
// p0 等于 0 返回 空
    b.eq    LReturnZero
#endif
// p0非空, 即走接受者存在流程
// 根據(jù)對(duì)象取isa(即從x0寄存器指向的地址 取出 isa),其中代碼已經(jīng)提示 p13 = isa
    ldr p13, [x0]       // p13 = isa
// 通過(guò) p16 = isa(p13) & ISA_MASK,拿出shiftcls信息,得到class信息
// GetClassFromIsa_p16 詳細(xì)源碼在下方
    GetClassFromIsa_p16 p13, 1, x0  // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
// 如果有isa,走CacheLookup (即緩存查找流程),也就是sel-imp快速查找
    CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS

// 下邊是小對(duì)象處理流程
// 如果是空返回nil, 不為空直接走下面的操作
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

.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */

#if SUPPORT_INDEXED_ISA
    // Indexed isa
// 將isa的值存入p16寄存器
    mov p16, \src           // optimistically set dst = src
// 判斷是否是 nonpointer isa
    tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f  // done if not non-pointer isa
    // isa in p16 is indexed
// 將_objc_indexed_classes所在的基址 讀入x10寄存器
    adrp    x10, _objc_indexed_classes@PAGE
// x10 = x10 + _objc_indexed_classes(page中的偏移量), x10基址 根據(jù) 偏移量 進(jìn)行 內(nèi)存偏移
    add x10, x10, _objc_indexed_classes@PAGEOFF
// 從p16的第ISA_INDEX_SHIFT位開(kāi)始,提取 ISA_INDEX_BITS 位到p16寄存器,剩余的高位用0補(bǔ)充
    ubfx    p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
    ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
// 用于64位系統(tǒng)
#elif __LP64__
.if \needs_auth == 0 // _cache_getImp takes an authed class already
    mov p16, \src
.else
    // 64-bit packed isa
    ExtractISA p16, \src, \auth_address
.endif
#else
// 用于32位系統(tǒng)
    // 32-bit raw isa
    mov p16, \src

#endif

.endmacro

大致可分為以下幾步

第一步

判斷objc_msgSend方法的第一個(gè)參數(shù)接收者receiver是否為空, 不為空繼續(xù)往下走

  • 如果支持tagged pointer(小對(duì)象類型), 跳轉(zhuǎn)LNilOrTagged

    • 如果小對(duì)象為空, 則返回空(LReturnZero)
    • 如果小對(duì)象不為空, 則找到小對(duì)象的isa, 接著走下一步
  • 如果即不是tagged pointer(小對(duì)象類型), receiver也不為空, 則

    • receiver中取出isa存入p13寄存器
    • 通過(guò) GetClassFromIsa_p16中,arm64架構(gòu)下通過(guò) isa & ISA_MASK 獲取shiftcls位域的類信息,接著走下一步
第二步

獲取完類信息完畢,進(jìn)入慢速查找流程CacheLookup NORMAL, 看下CacheLookup 源碼

#if defined(__arm64__) && __LP64__
#if TARGET_OS_OSX || TARGET_OS_SIMULATOR
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#endif
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif


.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
    //
    // Restart protocol:
    //
    //   As soon as we're past the LLookupStart\Function label we may have
    //   loaded an invalid cache pointer or mask.
    //
    //   When task_restartable_ranges_synchronize() is called,
    //   (or when a signal hits us) before we're past LLookupEnd\Function,
    //   then our PC will be reset to LLookupRecover\Function which forcefully
    //   jumps to the cache-miss codepath which have the following
    //   requirements:
    //
    //   GETIMP:
    //     The cache-miss is just returning NULL (setting x0 to 0)
    //
    //   NORMAL and LOOKUP:
    //   - x0 contains the receiver
    //   - x1 contains the selector
    //   - x16 contains the isa
    //   - other registers are set as per calling conventions
    //

    mov x15, x16            // stash the original isa
LLookupStart\Function:
//  p1 = SEL, p16 = isa
//  #define CACHE (2 * __SIZEOF_POINTER__), 其中 __SIZEOF_POINTER__表示pointer的大小 ,即 2*8 = 16

//  OSX 系統(tǒng) 或者 64位模擬器
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    ldr p10, [x16, #CACHE]              // p10 = mask|buckets

// p10 (cache) = mask|buckets  從x16(isa)中平移16字節(jié)得到, 存到p10
// 接下來(lái), 取出mask, 取出buckets

// p10(cache) 右移48位得到p11, p11為mask

// p10(cache)  & 0x0000ffffffffffff , 之后 p10為buckets
// 這里其實(shí)是mask高16位抹零,得到buckets 存入p10寄存器,  即去掉mask,留下buckets

//  x12為_(kāi)cmd & mask

    lsr p11, p10, #48           // p11 = mask
    and p10, p10, #0xffffffffffff   // p10 = buckets
    and w12, w1, w11            // x12 = _cmd & mask

// 其余arm架構(gòu) 64位機(jī)器, 大部分方法與上面類似
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16

// 真機(jī)留意下p11為cache = mask|buckets  
// 方式照舊 x16(isa)中平移16字節(jié),取出cache 存入p11寄存器
// isa距離cache 正好16字節(jié):isa(8字節(jié))-superClass(8字節(jié))-cache(mask高16位 + buckets低48位)

    ldr p11, [x16, #CACHE]          // p11 = mask|buckets

// 如果配置使用了緩存, 走下邊
#if CONFIG_USE_PREOPT_CACHES

// p11(cache)  & 0x0000ffffffffffff , 之后 得到buckets 存入寄存器p10
// 跟上面一樣, mask高16位抹零,得到buckets 存入p10寄存器,  即去掉mask,留下buckets
// 下面這里判斷是否是真機(jī)還是其他系統(tǒng), 區(qū)別僅僅順序不一樣, 都是 p10 = buckets操作
#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

// 新版的cache取值同樣做了一個(gè)判斷前置是否有cache操作
// #if CONFIG_USE_PREOPT_CACHES
// 如果有就平移7位做個(gè)異或操作
//     value ^= value >> 7;
// #endif
//     return (mask_t)(value & mask);
// 下面類似, 其實(shí)就是 x12 = _cmd & mask
// p11(cache)右移48位,得到mask(即p11 存儲(chǔ)mask), mask & p1(msgSend的第二個(gè)參數(shù) cmd-sel) ,得到sel-imp的下標(biāo)index(即搜索下標(biāo)) 存入p12(cache insert寫入時(shí)的哈希下標(biāo)計(jì)算是 通過(guò) sel & mask,讀取時(shí)也需要通過(guò)這種方式)

    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

// 非64位真機(jī) 
#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

// p13是下標(biāo) p10是buckets數(shù)組首地址,下標(biāo) 偏移16 得到實(shí)際內(nèi)存的偏移量,通過(guò)buckets的首地址偏移,獲取bucket存入p13寄存器

// PTRSHIFT是3, 1+3 = 4, 1<<4 即 偏移2^4 = 16

// LSL #(1+PTRSHIFT)-- 實(shí)際含義就是得到一個(gè)bucket占用的內(nèi)存大小 -- 相當(dāng)于mask = occupied -1-- _cmd & mask -- 取余數(shù)
  
    add p13, p10, p12, LSL #(1+PTRSHIFT)
                        // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

                        // do {
// 從x13(即p13)中取出 bucket 分別將imp和sel 存入 p17(存儲(chǔ)imp) 和 p9(存儲(chǔ)sel)
1:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
// 比較查詢的sel 與 p1(傳入的參數(shù)cmd)是否相同
    cmp p9, p1              //     if (sel != _cmd) {
// 不相等跳轉(zhuǎn)3f
    b.ne    3f              //         scan more
                        //     } else {
// 如果相等, 即找到查詢的方法, cacheHit 緩存命中,直接返回imp
2:  CacheHit \Mode              // hit:    call or return imp
                        //     }
// 如果sel == 0, 走miss
3:  cbz p9, \MissLabelDynamic       //     if (sel == 0) goto Miss;
// 判斷p13(下標(biāo)對(duì)應(yīng)的bucket) 是否 等于 p10(buckets數(shù)組第一個(gè)元素)
    cmp p13, p10            // } while (bucket >= buckets)
// bucket >= buckets 回到1b循環(huán)操作
    b.hs    1b

    // wrap-around:
    //   p10 = first bucket
    //   p11 = mask (and maybe other bits on LP64)
    //   p12 = _cmd & mask
    //
    // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
    // So stop when we circle back to the first probed bucket
    // rather than when hitting the first bucket again.
    //
    // Note that we might probe the initial bucket twice
    // when the first probed slot is the last entry.

// 令 p13 = buckets + (mask << 1+PTRSHIFT)
#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 {
// 向前查找 {imp, sel} = *bucket--
4:  ldp p17, p9, [x13], #-BUCKET_SIZE   //     {imp, sel} = *bucket--
// 再次比較 `sel(p9)` 與 `cmd(p1)`是否相等
    cmp p9, p1              //     if (sel == _cmd)
// 相等, 走2b, 即找到查詢的方法, `cacheHit` 緩存命中,直接返回`imp`
    b.eq    2b              //         goto hit
// 如果while (sel != 0 &&bucket > first_probed) 循環(huán)走4b方法
    cmp p9, #0              // } while (sel != 0 &&
    ccmp    p13, p12, #0, ne        //     bucket > first_probed)
    b.hi    4b

LLookupEnd\Function:
LLookupRecover\Function:
    b   \MissLabelDynamic

#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls)
    and p10, p11, #0x007ffffffffffffe   // p10 = buckets
    autdb   x10, x16            // auth as early as possible
#endif

    // x12 = (_cmd - first_shared_cache_sel)
    adrp    x9, _MagicSelRef@PAGE
    ldr p9, [x9, _MagicSelRef@PAGEOFF]
    sub p12, p1, p9

    // w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
    // bits 63..60 of x11 are the number of bits in hash_mask
    // bits 59..55 of x11 is hash_shift

    lsr x17, x11, #55           // w17 = (hash_shift, ...)
    lsr w9, w12, w17            // >>= shift

    lsr x17, x11, #60           // w17 = mask_bits
    mov x11, #0x7fff
    lsr x11, x11, x17           // p11 = mask (0x7fff >> mask_bits)
    and x9, x9, x11         // &= mask
#else
    // bits 63..53 of x11 is hash_mask
    // bits 52..48 of x11 is hash_shift
    lsr x17, x11, #48           // w17 = (hash_shift, hash_mask)
    lsr w9, w12, w17            // >>= shift
    and x9, x9, x11, LSR #53        // &=  mask
#endif

    ldr x17, [x10, x9, LSL #3]      // x17 == sel_offs | (imp_offs << 32)
    cmp x12, w17, uxtw

.if \Mode == GETIMP
    b.ne    \MissLabelConstant      // cache miss
    sub x0, x16, x17, LSR #32       // imp = isa - imp_offs
    SignAsImp x0
    ret
.else
    b.ne    5f              // cache miss
    sub x17, x16, x17, LSR #32      // imp = isa - imp_offs
.if \Mode == NORMAL
    br  x17
.elseif \Mode == LOOKUP
    orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
    SignAsImp x17
    ret
.else
.abort  unhandled mode \Mode
.endif

5:  ldursw  x9, [x10, #-8]          // offset -8 is the fallback offset
    add x16, x16, x9            // compute the fallback isa
    b   LLookupStart\Function       // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES

.endmacro
第三步

isa首地址平移16字節(jié)取cache, 因?yàn)樵?code>objc_class中, cache與首地址距離正好相差16字節(jié)( isa8字節(jié),superclass8字節(jié)。

第四步

因?yàn)?code>cache中高16位存mask,低48位存buckets,即p11 = cache = mask | buckets 。從cache中取出bucketsmask,并由mask根據(jù)哈希算法計(jì)算出哈希下標(biāo)

  • cache掩碼(0x0000ffffffffffff)& 運(yùn)算,將高16位mask抹零,得到buckets指針地址,即p10 = buckets

  • cache右移48位,得到mask,即p11 = mask

  • objc_msgSend的參數(shù)p1(即第二個(gè)參數(shù)_cmd)& mask,通過(guò)哈希算法,得到需要查找存儲(chǔ)sel-impbucket下標(biāo)index,即p12 = index = _cmd & mask。因?yàn)樵诖鎯?chǔ)sel-imp時(shí),也是通過(guò)同樣哈希算法計(jì)算哈希下標(biāo)進(jìn)行存儲(chǔ),所以讀取也需要通過(guò)同樣的方式讀取

下面是mask_t cache_hash源碼

sel-imp
#if defined(__arm64__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST
#define CONFIG_USE_PREOPT_CACHES 1
#else
#define CONFIG_USE_PREOPT_CACHES 0
#endif

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
    value ^= value >> 7;
#endif
    return (mask_t)(value & mask);
}
第五步

根據(jù)所得的哈希下標(biāo)indexbuckets首地址,取出哈希下標(biāo)對(duì)應(yīng)的bucket, 目的是為了進(jìn)而找到selimp

結(jié)構(gòu)體bucket_tsel8字節(jié),imp8字節(jié)

通過(guò)首地址 + 實(shí)際偏移量,獲取哈希下標(biāo)index對(duì)應(yīng)的bucket

第六步

根據(jù)獲取的bucket, 取出imp, sel, 令p17(存儲(chǔ)imp)p9(存儲(chǔ)sel)

第七步
  • 比較 bucketsel(p9)cmd(p1)是否相等

    • 相等跳轉(zhuǎn)2b, 即找到查詢的方法, cacheHit 緩存命中,直接返回imp

    • 不相等跳轉(zhuǎn)3f
      sel == 0 : goto miss, 走miss方法, 跳轉(zhuǎn)至objc_msgSend_uncached即慢速查找流程
      ② 相等, 判斷p13(下標(biāo)對(duì)應(yīng)的bucket) 是否 等于 p10(buckets數(shù)組第一個(gè)元素)
      // p10 = first bucket

      ③ 如果bucket > =buckets的第一個(gè)元素,回到1b進(jìn)行遞歸循環(huán)對(duì)比 if (sel != _cmd)

      ④ 如果bucket <buckets, 往下走

  • p13 = buckets + (mask << 1+PTRSHIFT)

  • 先做向前查找{imp, sel} = *bucket-- , 再次比較 sel(p9)cmd(p1)是否相等

    • 相等, 走2b, 即找到查詢的方法, cacheHit 緩存命中,直接返回imp
    • 不相等 如果while (sel != 0 &&bucket > first_probed) 循環(huán)走4b方法


總結(jié)

由于源碼比較復(fù)雜, 接下來(lái)我們模擬下快速查找

// 獲取當(dāng)前對(duì)象
id person = 0x10000 //隨意舉個(gè)例子
// 獲取當(dāng)前isa
isa_t isa = 0x000000
// 獲取當(dāng)前class isa → class → cache
// 因?yàn)閕sa才能獲取class, 進(jìn)而獲取cache
cache_t cache = isa + 16字節(jié)

// 進(jìn)入 CacheLookup 方法
// 由于arm64下
cache = mask|buckets
// buckets 可由cache & 掩碼0x0000ffffffffffff 得到
buckets = cache & 0x0000ffffffffffff
// mask 可由cache右移48位得到
mask = cache lsr #48
// 得到需要查找存儲(chǔ)sel imp的bucket下標(biāo)index, index = _cmd(傳入的參數(shù)p1) & mask
index = _cmd & mask  /  (_cmd ^ (_cmd >> 7)) & mask
// 舉個(gè)例子: person 有個(gè)sayHello方法, 那么我們要找到sayHello, 需要先找到bucket, 因?yàn)閎ucket里面才有sel和imp
[person sayHello] → imp (cache → bucket → (sel imp))

// 從buckets中遍歷查找對(duì)應(yīng)下標(biāo)的bucket值 (sel + imp = 16, 即下標(biāo)多少個(gè)就平移多少個(gè)bucket)
bucket = buckets + ((_cmd & mask) << (1+PTRSHIFT))
        = buckets +  index * 16

//// 判斷bucket里面的sel, 是否匹配cmd
//if (sel == 0) {
//    // 走miss方法, 跳轉(zhuǎn)至objc_msgSend_uncached即慢速查找流程
//    goto miss
//}else if (bucket.sel == _cmd) {
//    // 如果相等, 緩存命中
//    return imp
//}else {
//    // 如果不相等, 進(jìn)入第二層判斷
//    if (bucket == buckets) {
//        buckets + (mask << 1+PTRSHIFT)
//    }
//}

do {
    // 向前查找 {imp, sel} = *bucket-- (bucket = bucket - 1)
    bucket--
    // 判斷bucket里面的sel, 是否匹配cmd
    if (bucket.sel == _cmd) {
        // 如果相等, 緩存命中
        return imp
        
    }else {
        // bucket.sel != _cmd 方法
        
        // 走miss方法, 跳轉(zhuǎn)至objc_msgSend_uncached即慢速查找流程
        if (bucket.sel == 0) {
            goto miss
        }
    }
}while (bucket >= buckets)

//留意下這個(gè)相比之前781源碼會(huì)做2次do while, 分離開(kāi)了goto miss 以及 sel == 0 方法

// 人為設(shè)置bucket為最后一個(gè)元素
bucket =  buckets + (mask << 1+PTRSHIFT)

do {
    // 指針平移, 緩存查找順序向前查找 {imp, sel} = *bucket-- (bucket = bucket - 1)
    bucket--
    if (bucket.sel == _cmd) {
        // 如果相等, 緩存命中
        // 即 sel = bucket.sel, imp = bucket.imp,
        return imp
        
    }
    
}while ( sel != 0 && bucket > first_probed(first probed bucket 第一個(gè)元素))


如果快速查找沒(méi)有找到就開(kāi)啟慢速查找
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 178,610評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,939評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,668評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 56,004評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 43,173評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,426評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,656評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,833評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 35,247評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,580評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,371評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,621評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容