前言
前3篇文章简单的探索了class_data_bits_t,现在开始cache_t的探究。
什么是cache_t?作用是什么?
cache_t缓存,为了限制对频繁访问方法定义的方法列表进行线性搜索的需要(这种操作会大大降低方法查找的速度),Objective-C运行时函数在objc_cache数据结构中存储指向类中最近调用方法定义的指针,源自官方文档。
struct objc_cache {
unsigned int mask;
unsigned int occupied;
Method buckets[1];
};
解释:
mask:一个整数,指定已分配的缓存桶总数(减1)。在方法查找期间,Objective-C运行时使用该字段确定开始桶数组线性搜索的索引。指向方法选择器的指针使用逻辑与操作(index = (mask & selector))对该字段进行屏蔽。这是一个简单的哈希算法。occupied:表示已使用的缓存桶总数。buckets:指向方法数据结构的指针数组。这个数组不能包含超过掩码+ 1的项。注意,指针可以是NULL,表示缓存桶未被占用,并且被占用的桶可能不是连续的。这个数组可能会随着时间的推移而增长。 以上都是官方对cache_t的解释,看完这些定义我们来自己研究。
一、cache_t的结构
打开源码,首先我们找到objc_class,如图:
objc_class的一个成员变量cache,类型是cache_t,这就是我们的研究对象。在前面的探究中我们知道isa和superclass都是指针8位,所以首地址平移16位就是cache,这里记下以便后面的探究。
1、源码中的cache_t
从上面的源码中我们可以跳到cache_t,如图:
为了方便阅读我们省略了一些宏判断,如图所示,圈起来的方法是我们获取相关变量的方法,
mask()与occupied()的返回值都是mask_t,mask_t的值如下所示:
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t SEL;
所以我们应该关注bucket_t,如下图所示:
在这里我们发现了
_sel和_imp,还有2个相关的获取方法。
2、lldb验证源码
首先我们创建LGPerson,声明:
实现:
然后使用并断点:
接下来断点调试:
这里我们找到了
mask和occupied分别为三,表示有3个缓存位置,其中1个存了数据。接下来我们找具体的方法,如图:
我们发现只有第
2号位置有缓存,第0和第1号位置为空,跟前面mask和occupied的值一样,接下来打印查看具体的sel和imp,如图:
成功打印出了
sayWork方法的sel和imp。
3、小结
cache_t中缓存存方法的bucket_t(桶子),桶子中存放在sel和imp。
二、脱离源码调试
用lldb调试有太多的限制,如需要配置好的源码、每次调试都需要从头开始等。如果可以脱离源码就可以任意调试了,那么如何脱离源码呢?拿我们上面cache_t的调试为例子,我们可以将需要的结构体换成我们自定义的,做到结构相同就可以了,所以试着写一些我们自己的objc_calss、class_data_bits_t、cache_t和bucket_t。如图:
然后调用:
运行:
这就打印出来了,是不是跟上面的
lldb一样?这样就不用一次次的一步一步的查找了。一次构建,终身受益。