阅读 149

OC 类原理探索:cache 结构分析补充

OC 类原理探索 系列文章

  1. OC 类原理探索:类的结构分析
  2. OC 类原理探索:类结构分析补充
  3. OC 类原理探索:属性的底层原理
  4. OC 类原理探索:cache_t 的结构分析
  5. OC 类原理探索:cache 结构分析补充

前言

OC 类原理探索:cache_t 的结构分析中对cache的结构进行了介绍,以insert函数为切入点分析了cache中成员变量的用途,今天对cache结构做一些补充和扩展。

准备工作

一、_bucketsAndMaybeMask 验证

OC 类原理探索:cache_t 的结构分析 中说过 _bucketsAndMaybeMask存储的是buckets的首地址,我们用lldb来进行验证:

image.png

  • _bucketsAndMaybeMaskValue16进制形式打印,结果是0x00000001006444a0
  • buckets()16进制形式打印结果也是0x00000001006444a0
  • 根据打印结果,可以证明_bucketsAndMaybeMask存储的就是buckets的首地址。

二、buckets() 解析

添加方法时,进行源码断点调试:

image.png

  • addrbuckets()的值相等,都是0x00000001003623a0

  • buckets()又是addr & bucketsMask得到的,说明bucketsMask取的是下面的值: image.png 用二进制表示: image.png

  • bucketsMask根据架构的不同,取值是有区别的:

    image.png image.png image.png image.png

三、imp 算法

bucket_t中的imp函数有这样一段代码return (IMP)(imp ^ (uintptr_t)cls);如下:

image.png

  • 这里涉及到编码解码的过程;
  • 存储的imp是一个uintptr_t(长整形)的数值,并不是一个函数指针
  • 通过^(异或)操作获取正真的指针地址,但是为什么要这样操作呢,是因为在存储(编码)的时候做了同样的操作,我们继续往下看。

编码解码 算法

查看set函数以及其中的encodeImp编码函数:

image.png

  • ^(异或)算法
    • c = a ^ b
    • a = c ^ b
  • imp的编解码
    • set时, imp = 函数指针 ^ cls
    • 取的时候, 函数指针 = imp ^ cls
    • cls是算法的盐。

lldb 编码解码

断点调试imp函数:

image.png

进行lldb调试:

image.png

通过lldb最终得到say1方法,验证了上面的结论。

四、LLDB 调用方法 mask 默认为 7

image.png

  • OC 类原理探索:cache_t 的结构分析 中我们用正常代码调用say1方法,_maybeMask的值是3,而这里却变成了7这是为什么呢?
  • _maybeMask的值是7,说明有过一次扩容,这次扩容应该是在插入say1方法之前或者插入say1方法时,因为在之后也就没有必要插入那些方法了,我们在插入say1方法时做一些打印来进行分析。

insert函数中插入下面的代码:

image.png

运行程序,lldb调试:

image.png

  • 如上可得在插入say1方法前,先插入了respondsToSelectorclass,在插入say1时就会进行扩容,扩容时清空掉了前面的方法,所以最终_maybeMask的值是7_occupied的值是1
  • 我们还发现buckets第四个存储单元的imp存储的是首地址0x101906760,这也解释了为什么让_maybeMask = capacity - 1,也可在objc源码中找到相应方法。

为什么提前插入 respondsToSelector 和 class

关于为什么提前插入了respondsToSelectorclassllvm源码中也有相关介绍;

image.png

buckets 最后一个存储单元

buckets最后一个存储单元的imp,存储了buckets的首地址,在objc源码中也可找到相应方法。

image.png

五、cache_next 的真机环境

查看cache_next相关源码:

image.png

  • arm环境是我们的真机环境,在真机环境下,cache_next是不停的去前一个位置找空值;
  • 在非真机环境下,cache_next会不停的去后一个位置找空值。

六、insert() 函数之前做了什么

我们已经对insert()有了一定了解,那insert()函数之前做了什么呢?我们对->insert进行搜索,看看哪儿里调用了它。

image.png image.png

我们并没有找到cache相关的insert()函数,怎么办呢,我们可以打个断点看看堆栈信息。

image.png

拿到log_and_fill_cache进行搜索,在lookUpImpOrForward()中找到函数的调用。

image.png

除了这个方法,我们在objc-cache.mm中还可以看到下面的注释:

image.png

我们发现在insert之前调用了objc_msgSendobjc_msgSend是非常重要的一个方法,下一篇将对objc_msgSend进行探索。

文章分类
iOS
文章标签