OC底层原理(8)-cache_t原理(随记)

198 阅读3分钟

一、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,并在此时清理旧内存,最后就是继续填充重复操作。如下图