准备工作
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 就两个属性, 方法的 sel 和 imp 所以 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 { }
};
查看类的缓存
所有工作准备就续, 接下来就让我们来调试一波;
- 没有调用方法之前, 先来看一下
cache,_occupied = 0很明显还没有数据. - 继续往下走一步断点. 调用
sayNB这个方法. 然后再来看cache, 此时_occupied = 1, 说明已经缓存了一个方法了. - 接着我们来找一下这个缓存的方法, 看看是不是我们调用的
sayNB这个方法.经过一波调试打印, 我们确认就是我们调用的sayNB这个方法. - 继续往下走一步断点, 调用
sayHello这个方法. 然后再来看cache, 此时_occupied = 2, 说明已经缓存了两个方法了. - 接着我们来找一下这个缓存的方法, 看看是不是我们调用的
sayHello这个方法. 果然就是我们要找的.
总结
结过我们努力的调试验证, 相信对类的 cache 有了一个清晰的认识. 方法经过调用之后就存储在 cache, 这样当下次再调用的时候, 就可以直接在缓存中找到并调用, 不需要再去方法列表查找然后加载到内存, 可以提高效率和性能.