OC 类内存布局之 cache

309 阅读2分钟

准备工作

OC对象在调用方法之后, 这个方法的信息会存在类的缓存中, 方便下次快速调用. 具体的准备工作请稳步# OC 类内存布局之属性-成员-方法, 这里只把类的底层结构拿过来. 类的缓存就是 cache 这个成员, 所以我们的重点就是研究 cache_t;

通过查看 objc 的源码, 可以发现类的底层结构 struct objc_class 如下, 继承自 struct objc_object, 我们的把源码整合一下, 实际就相当于有 4 个成员, 这里我们只看成员, 不管其他的. 很明显

objc 的源码版本: 818.2

struct objc_class {
    isa_t isa;               // 8字节
    Class superclass;        // 8字节
    cache_t cache;           // 16字节   
    class_data_bits_t bits;  // 8字节
    
    // 方法定义太多, 暂时省略...
    class_rw_t *data() const {
        return bits.data();
    }
}

简化后的 cache_t 结构, 源码有各种架构判断什么的. 比较费劲, 简化保留重点, 比较容易理解, _occupied 表示已经缓存的方法数量. buckets() 方法功能是获取存储方法的数组. 数组是 bucket_t 类型的, bucket_t 就两个属性, 方法的 selimp 所以 bucket_t 结构也是重点;

struct cache_t {
    uintptr_t  _bucketsAndMaybeMask; 
    mask_t     _maybeMask; 
    uint16_t   _flags; 
    uint16_t   _occupied; // 已经缓存的数量
    
    // 方法
    struct bucket_t *buckets() const;
}; 
struct bucket_t {
    uintptr_t _imp;
    SEL _sel;

    // 方法, 具体实现就省略了
    SEL sel() const { } // 获取方法的 sel
    IMP rawImp(objc_class *cls) const { }
    IMP imp(bucket_t *base, Class cls) const { }
};

查看类的缓存

所有工作准备就续, 接下来就让我们来调试一波;

  1. 没有调用方法之前, 先来看一下 cache, _occupied = 0 很明显还没有数据. image.png
  2. 继续往下走一步断点. 调用 sayNB 这个方法. 然后再来看 cache, 此时 _occupied = 1, 说明已经缓存了一个方法了. image.png
  3. 接着我们来找一下这个缓存的方法, 看看是不是我们调用的 sayNB 这个方法.经过一波调试打印, 我们确认就是我们调用的 sayNB 这个方法. 002.png
  4. 继续往下走一步断点, 调用 sayHello 这个方法. 然后再来看 cache, 此时 _occupied = 2, 说明已经缓存了两个方法了. 003.png
  5. 接着我们来找一下这个缓存的方法, 看看是不是我们调用的 sayHello 这个方法. 果然就是我们要找的. 5840F961-1901-41A0-BA3C-94A2BE656CE1.png

总结

结过我们努力的调试验证, 相信对类的 cache 有了一个清晰的认识. 方法经过调用之后就存储在 cache, 这样当下次再调用的时候, 就可以直接在缓存中找到并调用, 不需要再去方法列表查找然后加载到内存, 可以提高效率和性能.