前言
今天我們要探究的cache_t
在之前的類的結(jié)構(gòu)分析中看到過(guò)沾谓,在objc_class
中存在一個(gè)cache_t
類型的成員cache
破喻,cache
顧名思義緩存
职祷,那存的是什么呢盖灸?
cache存的是什么
首先我們看一下cache_t
的源碼實(shí)現(xiàn):
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
//macOS蚁鳖、模擬器
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//真機(jī) 64位
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
// How much the mask is shifted by.
static constexpr uintptr_t maskShift = 48;
// Additional bits after the mask which must be zero. msgSend
// takes advantage of these additional bits to construct the value
// `mask << 4` from `_maskAndBuckets` in a single instruction.
static constexpr uintptr_t maskZeroBits = 4;
// The largest mask value we can store.
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
// Ensure we have enough bits for the buckets pointer.
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
//真機(jī) 非64位
// _maskAndBuckets stores the mask shift in the low 4 bits, and
// the buckets pointer in the remainder of the value. The mask
// shift is the value where (0xffff >> shift) produces the correct
// mask. This is equal to 16 - log2(cache_size).
explicit_atomic<uintptr_t> _maskAndBuckets;
mask_t _mask_unused;
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
//部分代碼省略
...
}
這里進(jìn)行了一個(gè)架構(gòu)的判斷:
1、CACHE_MASK_STORAGE_OUTLINED
表示macOS
或模擬器
2赁炎、CACHE_MASK_STORAGE_HIGH_16
表示64位的真機(jī)
3醉箕、CACHE_MASK_STORAGE_LOW_4
表示非64位的真機(jī)
我們可以看到它們都有一個(gè)explicit_atmic(顯示原子性)
,使用這個(gè)主要是確保增刪改查
時(shí)的安全性徙垫,而模擬器
中的_buckets
和_mask
在真機(jī)
中它們合并成了_maskAndBuckets
讥裤,這樣做的目的是為了節(jié)省內(nèi)存,讀取方便
姻报。
bucket
是桶
的意思己英,哪里面裝的是什么呢?這里我們進(jìn)入bucket_t
看一下:
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
//部分代碼省略
...
}
我們可以看到里面裝的是sel
和imp
吴旋,同時(shí)這里也進(jìn)行了架構(gòu)的判斷:分為真機(jī)
與非真機(jī)
损肛,區(qū)別在于sel
與imp
的順序不同。
總結(jié)
cache
中存儲(chǔ)的是sel-imp
cache中查找sel-imp
既然cache_t
中存的是sel-imp
邮府,哪又是如何查找的呢荧关?
通過(guò)源碼查找
首先我們定義一個(gè)LGPerson
類,聲明了兩個(gè)屬性
以及5個(gè)方法
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *lgName;
@property (nonatomic, strong) NSString *nickName;
- (void)sayHello;
- (void)sayCode;
- (void)sayMaster;
- (void)sayNB;
+ (void)sayHappy;
@end
在main
函數(shù)中執(zhí)行如下代碼褂傀,并在執(zhí)行第一個(gè)方法處打個(gè)斷點(diǎn):
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LGPerson *p = [LGPerson alloc];
Class pClass = [LGPerson class];
// p.lgName = @"Cooci";
// p.nickName = @"KC";
// 緩存一次方法 sayHello
// 4
[p sayHello];
[p sayCode];
[p sayMaster];
// [p sayNB];
NSLog(@"%@",pClass);
}
return 0;
}
這時(shí)我們通過(guò)lldb
進(jìn)行調(diào)試:
可以看出方法執(zhí)行前后忍啤,
_buckets
、_mask
仙辟、_occupied
都發(fā)生了變化同波,我們發(fā)現(xiàn)有一個(gè)struct bucket_t *buckets()
可以打印buckets
:但是
_sel
和_imp
并不是我們要的,我們通過(guò)sel()
和imp(Class)
打印看看:發(fā)現(xiàn)這時(shí)打印出來(lái)的就是我們剛剛執(zhí)行的方法sayHello
叠国,說(shuō)明在沒(méi)有調(diào)用方法前未檩,cache
是沒(méi)有緩存的,在調(diào)用一次后cache
中就有了一個(gè)緩存粟焊,即調(diào)用一次方法就會(huì)緩存一次方法