OC 类原理探索 系列文章
- OC 类原理探索:类的结构分析
- OC 类原理探索:类结构分析补充
- OC 类原理探索:属性的底层原理
- OC 类原理探索:cache_t 的结构分析
- OC 类原理探索:cache 结构分析补充
前言
OC 类原理探索:cache_t 的结构分析中对cache的结构进行了介绍,以insert函数为切入点分析了cache中成员变量的用途,今天对cache结构做一些补充和扩展。
准备工作
一、_bucketsAndMaybeMask 验证
OC 类原理探索:cache_t 的结构分析 中说过
_bucketsAndMaybeMask存储的是buckets的首地址,我们用lldb来进行验证:
_bucketsAndMaybeMask的Value以16进制形式打印,结果是0x00000001006444a0;buckets()的16进制形式打印结果也是0x00000001006444a0;- 根据打印结果,可以证明
_bucketsAndMaybeMask存储的就是buckets的首地址。
二、buckets() 解析
添加方法时,进行源码断点调试:
-
addr和buckets()的值相等,都是0x00000001003623a0。 -
buckets()又是addr & bucketsMask得到的,说明bucketsMask取的是下面的值:用二进制表示:
-
bucketsMask根据架构的不同,取值是有区别的:
三、imp 算法
bucket_t中的imp函数有这样一段代码return (IMP)(imp ^ (uintptr_t)cls);如下:
- 这里涉及到
编码和解码的过程; - 存储的
imp是一个uintptr_t(长整形)的数值,并不是一个函数指针; - 通过
^(异或)操作获取正真的指针地址,但是为什么要这样操作呢,是因为在存储(编码)的时候做了同样的操作,我们继续往下看。
编码解码 算法
查看set函数以及其中的encodeImp编码函数:
^(异或)算法c = a ^ b;a = c ^ b。
imp的编解码set时,imp = 函数指针 ^ cls;- 取的时候,
函数指针 = imp ^ cls; cls是算法的盐。
lldb 编码解码
断点调试imp函数:
进行lldb调试:
通过lldb最终得到say1方法,验证了上面的结论。
四、LLDB 调用方法 mask 默认为 7
- 在 OC 类原理探索:cache_t 的结构分析 中我们用正常代码调用
say1方法,_maybeMask的值是3,而这里却变成了7这是为什么呢? _maybeMask的值是7,说明有过一次扩容,这次扩容应该是在插入say1方法之前或者插入say1方法时,因为在之后也就没有必要插入那些方法了,我们在插入say1方法时做一些打印来进行分析。
在insert函数中插入下面的代码:
运行程序,lldb调试:
- 如上可得在插入
say1方法前,先插入了respondsToSelector和class,在插入say1时就会进行扩容,扩容时清空掉了前面的方法,所以最终_maybeMask的值是7,_occupied的值是1; - 我们还发现
buckets第四个存储单元的imp存储的是首地址0x101906760,这也解释了为什么让_maybeMask=capacity - 1,也可在objc源码中找到相应方法。
为什么提前插入 respondsToSelector 和 class
关于为什么提前插入了respondsToSelector和class,llvm源码中也有相关介绍;
buckets 最后一个存储单元
buckets最后一个存储单元的imp,存储了buckets的首地址,在objc源码中也可找到相应方法。
五、cache_next 的真机环境
查看cache_next相关源码:
arm环境是我们的真机环境,在真机环境下,cache_next是不停的去前一个位置找空值;- 在非真机环境下,
cache_next会不停的去后一个位置找空值。
六、insert() 函数之前做了什么
我们已经对insert()有了一定了解,那insert()函数之前做了什么呢?我们对->insert进行搜索,看看哪儿里调用了它。
我们并没有找到cache相关的insert()函数,怎么办呢,我们可以打个断点看看堆栈信息。
拿到log_and_fill_cache进行搜索,在lookUpImpOrForward()中找到函数的调用。
除了这个方法,我们在objc-cache.mm中还可以看到下面的注释:
我们发现在insert之前调用了objc_msgSend,objc_msgSend是非常重要的一个方法,下一篇将对objc_msgSend进行探索。