一、cache_t结构分析 上篇是对类的分析,cache_t 就是 Class 结构体组成的一部分,根据字面意思可以理解为缓存
然后这一篇就具体分析一下 cache_t 这个结构体,首先了解一下这个结构体的结构 结构体的第一个成员变量为_buckets ,所以先看一下 bucket_t 这个结构体 这里主要看 arm64 对应的成员变量,可以隐约看出来这里缓存的就是方法,缓存的方法主要有 3 部分组成接下来通过打印来分析 cache_t 里存储的类容
通过 Class 的结构体可知要读取到 cache,地址就需要偏移 ISA(8字节)+ superclass(8字节)= 16 (chache 的首地址),转换为16进制就是加 0x10 0x100002658 + 0x10 = 0x100002668;因为0x100002668是cache_t的指针地址,所以读取地址时需要加(cache_t *) 进行转换,最后我们看到cache_t 的内容。看到只有 _buckets 有内容,于是就开始打印 很神奇的发行啥也没有,那东西都去哪了?先回想cache_t 干什么的?--- 缓存方法。所以在第一次调用的时候,缓存是肯定为空的。于是我写一个方法,并调用看看结果如何。 此时可以看到 _buckets 不再为空。说明在调用一次之后就有缓存了。问题:为什么调用的 alloc 就没有被缓存呢?
因为 alloc 是类方法,存在元类里。
以上都是通过打印而得到的答案,接下来就来揭开另一种验证方式---模拟一个class 结构体,贴出代码
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
typedef uintptr_t cache_key_t;
struct ty_bucket_t {
IMP _imp;
cache_key_t _key;
};
struct ty_cache_t {
struct ty_bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
struct ty_class_data_bits_t {
uintptr_t bits;
};
struct ty_objc_class {
Class ISA;
Class superclass;
struct ty_cache_t cache;
struct ty_class_data_bits_t bits;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class pClass = person.class;
[person goodMorning];
[person goodMorning];
struct ty_objc_class *ty_class = (__bridge struct ty_objc_class *)(pClass);
for (mask_t i = 0; i < ty_class->cache._mask; i++) {
struct ty_bucket_t bucket = ty_class->cache._buckets[i];
NSLog(@"%lu - %p", bucket._key, bucket._imp);
}
return 0;
}
打印结果:
总结: cache_t缓存->buctet -> 取容量(capcity),如果 capcity 为 0 时,就加 1 ;如果 capcity 超出 3/4,就扩容,就扩容到原来的2倍。然后在通过find方法,找到 key 通过哈希进行存储找到相应 bucket。
缓存流程:通过cache_fill_nolock 进入缓存入口,然后判断是否有缓存,如果有缓存就 return,如果没有就将 sel 转为 key 数字形式(cache_key_t key = getKey(sel))方便后面处理;然后读取开辟占用cache->occupied和读取容量cache->capacity并给一个新的临时变量(newOccupied,capacity), ,然后判断是否为一个空的缓存表,如果为空的,就进入cache->reallocate 创建一个bucket,这时mask为0,occupied为0.申请完毕之后开始缓,对Bucket填充,对key 和 imp赋值。如果不是一个空的缓存吧,就直接添加bucket。如果此时capacityz找到已申请容量的3/4就扩容,扩容要原来的两倍。然后就又开始reaclloc,并在此时清理旧内存,最后就是继续填充重复操作。如下图