前言
今天我们要探究的cache_t在之前的类的结构分析中看到过,在objc_class中存在一个cache_t类型的成员cache,cache顾名思义缓存,那存的是什么呢?
cache存的是什么
首先我们看一下cache_t的源码实现:
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
//真机 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
//真机 非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;
//部分代码省略
...
}
这里进行了一个架构的判断:
1、CACHE_MASK_STORAGE_OUTLINED表示macOS或模拟器
2、CACHE_MASK_STORAGE_HIGH_16表示64位的真机
3、CACHE_MASK_STORAGE_LOW_4表示非64位的真机
我们可以看到它们都有一个explicit_atmic(显示原子性),使用这个主要是确保增删改查时的安全性,而模拟器中的_buckets和_mask在真机中它们合并成了_maskAndBuckets,这样做的目的是为了节省内存,读取方便。
bucket是桶的意思,哪里面装的是什么呢?这里我们进入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,同时这里也进行了架构的判断:分为真机与非真机,区别在于sel与imp的顺序不同。
总结
cache中存储的是sel-imp
cache中查找sel-imp
既然cache_t中存的是sel-imp,哪又是如何查找的呢?
通过源码查找
首先我们定义一个LGPerson类,声明了两个属性以及5个方法
@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函数中执行如下代码,并在执行第一个方法处打个断点:
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;
}
这时我们通过lldb进行调试:

lldb-cache_t
可以看出方法执行前后,
_buckets、_mask、_occupied都发生了变化,我们发现有一个struct bucket_t *buckets()可以打印buckets:
buckets
但是
_sel和_imp并不是我们要的,我们通过sel()和imp(Class)打印看看:
sel

imp
发现这时打印出来的就是我们刚刚执行的方法sayHello,说明在没有调用方法前,cache是没有缓存的,在调用一次后cache中就有了一个缓存,即调用一次方法就会缓存一次方法