cache_t 数据结构
struct cache_t {
/**
explicit_atomic 显示原子性,目的是为了能够 保证 增删改查时 线程的安全性 等价于 struct bucket_t * _buckets;
_buckets 中放的是 sel imp
_buckets的读取 有提供相应名称的方法 buckets()
*/
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
// 省略代码。。。。。。。。。
mask_t mask() const;
// 省略代码。。。。。。。。。
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
public:
// 省略代码。。。。。。。。。
//_buckets的读取 有提供相应名称的方法 buckets()
struct bucket_t *buckets() const; //_buckets 中放的是 sel imp
Class cls() const;
mask_t occupied() const;
// 省略代码。。。。。。。。。
void insert(SEL sel, IMP imp, id receiver);
};
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 是加了原子性的保护
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
//省略代码。。。。。
}
通过lldb分析
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) int age;
@property (nonatomic, strong) NSString *hobby;
- (void)saySomething;
@end
Person *p = [Person alloc];
Class pClass = [Person class];
(lldb) p/x pClass
(Class) $2 = 0x0000000100008428 Person
(lldb) p (cache_t *)0x0000000100008438 //cache属性的获取,需要通过pclass的首地址平移16字节,即首地址+0x10获取cache的地址
(cache_t *) $3 = 0x0000000100008438
(lldb) p *$3
(cache_t) $4 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4298515392
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 0
}
}
_flags = 32808
_occupied = 0
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0000802800000000
}
}
}
}
(lldb) p $4.buckets() //从源码的分析中,我们知道sel-imp是在cache_t的_buckets属性中(目前处于macOS环境),而在cache_t结构体中提供了获取_buckets属性的方法buckets()
(bucket_t *) $5 = 0x00000001003623c0
(lldb) p *$5
(bucket_t) $6 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb) p [p saySomething]
-[Person saySomething]
(lldb) p $4.buckets()
(bucket_t *) $10 = 0x000000010143ae80
(lldb) p *$10
(bucket_t) $11 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb) p $4.buckets()[1]
(bucket_t) $12 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 47128
}
}
}
(lldb) p $12.sel()
(SEL) $13 = "saySomething"
(lldb) p $12.imp(nil,pClass)
(IMP) $14 = 0x0000000100003c30 (ObjcBuild`-[Person saySomething])
(lldb) p $4.buckets()[2]
(bucket_t) $15 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb)
由上可知,在没有执行方法调用时,此时的cache是没有缓存的,执行了一次方法调用,cache中就有了一个缓存,即调用一次方法就会缓存一次方法。
脱离源码分析
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct ypy_bucket_t {
SEL _sel;
IMP _imp;
};
struct ypy_cache_t {
struct ypy_bucket_t *_bukets; // 8
mask_t _maybeMask; // 4
uint16_t _flags; // 2
uint16_t _occupied; // 2
};
struct ypy_class_data_bits_t {
uintptr_t bits;
};
// cache class
struct ypy_objc_class {
Class isa;
Class superclass;
struct ypy_cache_t cache; // formerly cache pointer and vtable
struct ypy_class_data_bits_t bits;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
Class pClass = p.class; // objc_clas
// [p say1];
// [p say2];
// [p say3];
// [p say4];
// [p say1];
// [p say2];
// [p say3];
[pClass sayHappy];
struct ypy_objc_class *ypy_class = (__bridge struct ypy_objc_class *)(pClass);
NSLog(@"%hu - %u",ypy_class->cache._occupied,ypy_class->cache._maybeMask);
for (mask_t i = 0; i<ypy_class->cache._maybeMask; i++) {
struct ypy_bucket_t bucket = ypy_class->cache._bukets[i];
NSLog(@"%@ - %pf",NSStringFromSelector(bucket._sel),bucket._imp);
}
NSLog(@"Hello, World!");
}
return 0;
}
示例一
示例二
示例三
示例四
针对上面的打印结果,有以下几点疑问
- 1、_mask是什么?
- 2、_occupied 是什么?
- 3、为什么随着方法调用的增多,其打印的occupied 和 mask会变化?
- 4、bucket数据为什么会有丢失的情况?,例如示例三中,只有say1、say2方法有函数指针
- 5、方法的打印是有序的么?
- 6、init 为何会打印出来 带着上述的这些疑问,下面来进行cache底层原理的探索