第八節(jié)課 消息流程分析之快速查找(下)
上篇文章我們通過源碼查看了方法底層調(diào)用的邏輯说莫,但是只分析到了objc_msgSend
的主體邏輯臼勉,并沒有深入了解折晦,那么這篇文章我們就繼續(xù)深入嵌纲。
深入前在抛,小小的回顧還是有必要的
主要的流程:
1.判斷當(dāng)前接收者是否存在cmp p0, #0
2.判斷是不是SUPPORT_TAGGED_POINTERS
類型。如果是返吻,執(zhí)行b.le LNilOrTagged
,然后在里面執(zhí)行 b.eq LReturnZero
姑子。如果不是SUPPORT_TAGGED_POINTERS
類型,直接b.eq LReturnZero
思喊,此次objc_msgSend無效
壁酬,結(jié)束本次消息發(fā)送。
3.如果p0
存在恨课,則將x0
存入到p13
,x0
是receiver
,即類岳服,即類的首地址剂公,即isa
,也就是說p13=isa
吊宋。通過isa
拿到class
GetClassFromIsa_p16 p13, 1, x0
4.查看GetClassFromIsa_p16
源碼纲辽,最終獲取到p16=class
5.CacheLookup NORMAL
這篇文章我們就主要來分析這個CacheLookup NORMAL
CacheLookup 緩存查找匯編源碼
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 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$1,
// then our PC will be reset to LLookupRecover$1 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
#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 //真機架構(gòu)
ldr p11, [x16, #CACHE] //x16進行平移#CACHE大小,存儲到p11璃搜。CACHE全局搜索賦值拖吼,是2 * __SIZEOF_POINTER__也就是2倍的指針大小,也就是16.平移后的位置就相當(dāng)于cache_t的位置这吻。p11 = cache_t
// 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
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function //不為0就跳轉(zhuǎn)LLookupPreopt
#endif
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 //cache_t(_bucketsAndMaybeMask)右移48位吊档,即清空了buckets,可以得到mask唾糯,最后將p12 & mask怠硼,得到了第一次查找bucket_t的index鬼贱,即first_probed
// x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
// p11 cache -> p10 = buckets
// p11, LSR #48 -> mask
// p1(_cmd) & mask = index -> p12
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
// objc - 源碼調(diào)試 + 匯編
// p11 cache -> p10 = buckets
// p1(_cmd) & mask = index -> p12
// (_cmd & mask) << 4 -> int 1 2 3 4 5 地址->int
// buckets + 代表內(nèi)存平移 (1 2 3 4)
// b[i] -> b + I
// p13 當(dāng)前查找bucket
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
// *bucket-- p17, p9
// bucket 里面的東西 imp (p17) sel (p9)
// 查到的 sel (p9) 和我們 say1
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
主要分為以下幾步:
【第一步】通過cache
首地址平移16字節(jié)
(因為在objc_class中,cache正好距離首地址16字節(jié)香璃,即isa首地址 占8字節(jié)这难,superClass占8字節(jié)),獲取cache葡秒,cache中高16位存mask姻乓,低48位存buckets,即p11 = cache
【第二步】從cache中
分別取出buckets和mask
眯牧,并由mask根據(jù)哈希算法計算出哈希下標(biāo)
通過cache和掩碼
(即0x0000fffffffffffe)的 & 運算
蹋岩,將高16位mask抹零
,得到buckets指針地址炸站,即p10 = buckets
將cache右移48位
星澳,得到mask,即p11 = mask
將objc_msgSend
的參數(shù)p1
(即第二個參數(shù)_cmd)& msak
,通過哈希算法
旱易,得到需要查找存儲sel-imp的bucket下標(biāo)index
禁偎,即p12 = index = _cmd & mask
,為什么通過這種方式呢?因為在存儲sel-imp時
阀坏,也是通過同樣哈希算法計算哈希下標(biāo)進行存儲
如暖,所以讀取也需要通過同樣的方式讀取。
總結(jié):
文字版流程
- 首先判斷
recevier
是否存在 recevier -> isa -> class(p16)
-
class
通過內(nèi)存平移
->cache(bucket mask)
-
bucket掩碼
->bucket
-
mask掩碼
->mask
- insert 哈希函數(shù)
(mask_t)(value & mask)
- 得到第一次查找的
index
- bucket + index
bucket平移
獲取整個緩存里面的第幾個 bucket{imp sel}
- 拿到
sel
與_cmd
進行比較sel == _cmd -> cacheHit -> imp^isa = imp(br)調(diào)用imp
-
sel != _cmd
bucket再次平移
- 死循環(huán) 遍歷
- 找不到 ->
_objc_msgSend_uncached
圖片版流程