10.Class-cache_t
之前进行类的结构的分析的时候,我们还有一个重要的东西没有进行研究,就是 cache_t。
一、cache_t 的基本结构
和之前一样,我们在源码环境下编译调试。
回顾一下class的基础结构:
说明
ISA(结构体指针)
父类(结构体指针)
方法缓存
类的具体信息
大小(字节)
8
8
16
8
1.1 源码
其核心数据结构如下
struct cache_t {
// <<<< 主要数据结构
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
...
};_bucketsAndMaybeMask
_bucketsAndMaybeMask is a buckets_t pointer
它是一个
buckets_t类型的指针
_maybeMask
_maybeMask is the buckets mask
_occupied
_originalPreoptCache
1.2 LLDB调试
我们无法直接输出相关成员内容了,为了继续输出相关属性我们继续看源码
相关get方法
我们使用 buckets() 方法继续输出调试
这里你会发现用到了 $12.buckets()[2] 的下标是2,0和1都是空的。因为这里 buckets 不是数组,是哈希表的结构。
1.3、bucket_t的数据结构
包含了 SEL 和 IMP
二、自己实现 cache_t 数据结构
使用LLDB进行调试感觉到太麻烦了,为了更方便的进行调试我们尝试对数据结构进行模仿。
2.1 调试代码
输出:
2.2 扩容
我们调整一下代码
我们发现这里进行了扩容。这里又是什么逻辑呢?
三、insert 与扩容
为了更好的理解扩容,我们先了解一下方法缓存是怎么插入的。
3.1 核心源码解读
a. INIT_CACHE_SIZE 初始化缓存
通过源码发现,这里是通过位运算进行的缓存空间大小初始化。
如,当前我们的是 1 << 2 = 4。同时我们还能发现最大值为 1 << 16。
b. 扩容临界点
newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity)
// Cache is less than 3/4 or 7/8 full. Use it as-is.
真机为 7/8
c. 扩容操作
扩容按
x2进行扩容
d. 扩容后
我们看一下触发扩容的那次Log:
这次扩容后,我们发现。除了最新的,之前的缓存都没有了。
这是因为 扩容 并不是真正意义上的扩展之前的空间,而是重新开辟一块空间。
3.2 cache_hash 计算哈希下标
3.3 cache_next 寻找插入位置
3.4 set 差入逻辑
将方法插入缓存,并不是直接插入的。而是进行了编码。(uintptr_t)newImp ^ (uintptr_t)cls;
3.5 流程图

关于类型转换的思考
这里使用自己定义的类型进行转换,感觉是不是很懵,感觉这也行?
我们抛开代码,思考一下:我们为什么要定义各种数据结构?为了能读写数据。
而能正确读写数据的前提是,计算机能认识这些数据。而我们定义的数据结构,就是计算机认识他们的基准。
只要两个数据结构的内存结构一致,那么进行转换就没有问题。
参考
Last updated
Was this helpful?