(一)cache_t的結(jié)構(gòu)分析
我們首先來看cache_t的定義:
struct cache_t {
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
uint16_t _flags;
uint16_t _occupied;
public: //對外公開可以調(diào)用的方法
static bucket_t *emptyBuckets();
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
....
}
// 在手機真機環(huán)境下
struct cache_t {
struct bucket_t * _maskAndBuckets;
uint16_t _flags;
uint16_t _occupied;
public: //對外公開可以調(diào)用的方法
static bucket_t *emptyBuckets();
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
....
}
bucket_t的結(jié)構(gòu)
struct bucket_t {
private:
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
public:
inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); }
inline IMP imp(Class cls) const {
uintptr_t imp = _imp.load(memory_order::memory_order_relaxed);
if (!imp) return nil;
}
};
cache_t內(nèi)部結(jié)構(gòu)圖.png
在結(jié)構(gòu)體cache_t中
- _buckets:數(shù)組,是bucket_t結(jié)構(gòu)體的數(shù)組,bucket_t是用來存放方法的SEL內(nèi)存地址和IMP的;
- _mask的大小是數(shù)組大小 - 1,用作掩碼。(因為這里維護的數(shù)組大小都是2的整數(shù)次冪,所以_mask的二進制位000011, 000111, 001111)剛好可以用作hash取余數(shù)的掩碼。剛好保證相與后不超過緩存大小。
- _occupied是當前已緩存的方法數(shù)。即數(shù)組中已使用了多少位置。
- _maskAndBucket是mask和bucket的結(jié)合體,提升了性能和效率;
注意點:
在模擬器和macOS環(huán)境下,mache_t的結(jié)構(gòu):
mache_t.png
在手機真機環(huán)境下,mache_t的結(jié)構(gòu):
mache_t.png
二、通過LLDB來探索cache_t
在模擬器環(huán)境下:
- 通過p/x pClass ,來得到LGPerson類對象的首地址為0x0000000100002298
(lldb) p/x pClass
(Class) $0 = 0x0000000100002298 LGPerson
- 我們通過地址平移16字節(jié),0x0000000100002298->0x00000001000022a8的到cache_t的首地址,因為類里面先存isa(8字節(jié)),superclass(8字節(jié)) ,然后才是cache_t
(lldb) p (cache_t*) 0x00000001000022a8
(cache_t *) $1 = 0x00000001000022a8
- 得到cache_t之后,p *($1)來打印一下cache_t
(lldb) p *($1)
(cache_t) $2 = {
_buckets = {
std::__1::atomic<bucket_t *> = 0x00000001007604d0 {
_sel = {
std::__1::atomic<objc_selector *> = 0x0000000000000000
}
_imp = {
std::__1::atomic<unsigned long> = 0
}
}
}
_mask = {
std::__1::atomic<unsigned int> = 7
}
_flags = 32804
_occupied = 1
}
- 通過函數(shù)buckets()來獲取_buckets哈希表
(lldb) p $1->buckets()
(bucket_t *) $4 = 0x00000001007604d0
- 通過
p $4[0]
,打印_buckets
的第一個元素,通過下標獲取其他位置上的元素$4[1]
,$4[2]
,$4[3]
...
(lldb) p $4[0]
(bucket_t) $5 = {
_sel = {
std::__1::atomic<objc_selector *> = 0x0000000100000e3c
}
_imp = {
std::__1::atomic<unsigned long> = 11928
}
}
- 通過
p $5.sel()
,p $5.imp(pClass)
獲取sel
,imp
(lldb) p $5.sel()
(SEL) $6 = "sayHello"
(lldb) p $5.imp(pClass)
(IMP) $7 = 0x0000000100000c00 (KCObjc`-[LGPerson sayHello])
至此我們獲取到了我們想要的(sel,imp).
思考: 為什么我們有時候p $4[0]去獲取的時候,獲取到的是空?
原因: _buckets是一張哈希表不是數(shù)組,當添加一個(sel,imp)到哈希表中,index是通過一定的算法算出來的,當index=0位置還沒bucket存放的時候,就會為空。