类的结构分析 2 - cache_t
本文来探索类结构中 cache_t.
之前的文章 OC 底层探索 04 中,已知如何找到类信息。本文我们对类信息中的 cache_t 进行探索。
objc_class 结构 :
从 OC 底层探索 04 中的指针和内存偏移,我们已知可通过指针平移获取相应位置信息,cache_t 的位置 = 8 + 8 =16
一、cache_t 简析
cache_t 的源码分析:
CACHE_MASK_STORAGE:
1、支持架构
cache_t 源码有点长,我们可从截取的这部分代码中看到它对不同架构的支持:
MacOS:i386
模拟器:x86
真机:arm64
cache_t 中还可以发现一点,模拟器和真机的一些处理是不同的,业务开发中,我们所调试使用的最好的选择还是真机。
2、cache_t 内容
cache_t –> 缓存 –> 增删改查
模拟器:
bucket_t:
explicit_atomic –> 我们点击进去可以看到 它是一些 C++ 代码,而其中重要的内容是 ‘T’ –> struct bucket_t * 。关于它,在这里我们暂时只需要知道它是原子性,为了我们缓存的安全性即可,更深层的后续再做探究。
struct bucket_t :*imp sel
_mask
真机 64:
从下面代码,可观察到 maskAndBuckets 和一系列带有‘mask’ 的字段 –> 掩码、指针平移
1 |
|
_maskAndBuckets:–> bucket_t .
_mask_unused:可能是苹果的预留,不管它 <– “Don’t know how to do … …” .
另:
_flags:标记
_occupied:占位,内存占多少
–> cache_t 结构图:
二、cache_t 缓存了什么
1、cache_t 缓存了方法
运行工程 (部分测试代码可能存在偏差, 可自行编写),在未调用任何方法前,cache_t 内容:
标线所示的值均为 0,继续执行,p 调用方法:
对象 p 调用一次方法后,sel imp 不再为 0,_mask 3 、occupied+1 –>
推测:方法执行一次后缓存在 cache_t 中。(mask occupied 文章后半部分探究)
验证 _buckets 中存着调用过的方法:
cache_t 源码中寻找是否有获取 _buckets 的方法 :
继续 lldb 调试:
上图,可验证 –> 方法首次执行后缓存在 cache_t 中:
cache_t 中 sel 就是 对象 p 刚刚所调用方法的方法名,
imp 指向是 MyPerson 中的 方法的指针,指针地址 0x0000000100001b50.
2、cache_t 缓存集合 - buckets
多个方法调用
我们继续运行代码,让 p 调用方法 2:
由上可知:方法调用后都会存 buckets 中。
同样通过 OC 底层探索 04 的指针和内存偏移,通过数组 index 属性操作:
同样,我们取到了方法。
思考:方法再调用会怎么样呢?
方法只会缓存一份,方法调用的流程是什么样子的呢? –> 后续文章再对 objc_msgSend 流程进行探究。
2、cache_t 中 mask 和 occupied 是什么?
运行工程,调用多个方法,进行 lldb 调试. 如下图:
调试过程中,我们发现了几个问题:
1、occupied 和 mask 是什么?它们的值为何是一直在变化的?
2、cache_t 中方法的顺序和调用方法顺序为何不同?
3、buckets 中方法为何丢失不在了?
寻找答案:
1、去 cache_t 源码:
进入 mask() 和 occupied() 方法,发现没什么有用信息!
但看到下面 incrementOccupied() - occupied 增量:
源码中我们发现了 _occupied++ 和 mask() 的操作.
全局搜索 ‘incrementOccupied(’ –> cache_t 的 insert 中做了 occupied/mask 的处理
2、cache_t::insert 方法流程:
1 |
|
cache_t::insert 逻辑流程概况图:
从代码逻辑流程中,我们可以得到上面问题答案:
1、occupied 从 1->2->1->2->3 的原因:当 cache ≥ 3/4capacity 时,空间会重新开辟并释放旧的空间,同时 occupied 手动置 0.
2、mask 值变化原因: mask = capacity - 1,所以 它的值是 3 7 15……
3、方法的缓存与调用顺序:缓存时通过哈希算法:sel & mask 对 sel 存放位置 index 计算的,so 缓存是乱序的。
4、buckets 中方法丢失:因 occupied>2 空间会被重新开辟,旧的空间会被释放 free,之前的缓存的方法自然也会一起清掉。后面再掉的话会再次缓存。
do{}while() 的流程:
以上。
问题:cache_t::insert 什么时候调用呢?–> 方法调用流程 –> objc_msgSend 消息发送流后程续文章继续探索。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!