OC底层-cache_t分析

838 阅读5分钟

我们是从类的结构开始分析的,之前的文章我们探索了isaclass_data_bits_t,那么今天这篇文章我们开始探索cache_t,看下这个结构体干了什么事,系统设计这个结构体是为了做什么?带着疑问我们进入开始今天的探索。

知识补充

  • 数组:数组是用于储存多个相同类型数据的集合。主要有以下优缺点:

优点:访问某个下标的内容很方便,速度快

缺点:数组中进行插入、删除等操作比较繁琐耗时

  • 链表:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。主要有以下优缺点:

优点:插入或者删除某个节点的元素很简单方便

缺点:查找某个位置节点的元素时需要挨个访问,比较耗时

  • 哈希表:是根据关键码值而直接进行访问的数据结构。主要有以下优缺点:

优点:1、访问某个元素速度很快 2、插入删除操作也很方便

缺点:需要经过一系列运算比较复杂

Cache_t分析

1、首先我们先看下一个类,它的cache_t中存储些什么?下面我们通过lldb去看下结果,执行结果如下:

image.png

2、通过上图我们很难确定我们需要研究的数据,那么我们应该怎么办呢?下面我们先去看下cache_t这个结构体的定义:

image.png

3、这是结构体cache_t的定义,首先是有一个成员_bucketsAndMaybeMask,然后有一个联合体,联合体中又有两个成员,一个是_originalPreoptCache,一个是结构体,然后这个结构体中又有_maybeMask_flags_occupied三个成员,我们通过这个定义也无法确定那个是我们需要的成员,那么接下来应该怎么办呢?

341624781547_.pic.jpg

4、我们除了这些它的成员,还有它的一些函数,然我们去看下这些函数,看看能不能找到我们需要的结果呢?

361624781859_.pic.jpg

好多各种乱七八糟的东西,有点迷,但是我们静心去看,大概那么一瞅,发现总是围绕着一个bucket_t在处理数据,瞬间就有一个大胆的想法出现在脑海中,是否这个就是我们需要的呢?带着好奇,我们点击进去,然后发现:

image.png

5、出现了_imp_sel,我们都知道IMPSEL都是成对出现的,而且这个里面存储的是方法选择器和方法实现,那么是否是说明这个就是我们需要的?那么接下来我们去看一下苹果WWDC2020中对于runtime的优化讲解,通过视频大概了解一下苹果对于runtime的优化,然后我们继续回到我们的问题,那么我们此时此刻可以分析得到如下这个结构图:

未命名.png

6、那么我们通过苹果的视频讲解和一些简单的分析得出了这个结构图,怎么去验证它的正确性呢?下面开启我们的lldb调试模式,我们之前是位移32字节是为了访问class_data_bits_t,那么访问cache_t我们只需要位移16字节,操作流程如下:

image.png

391624783699_.pic.jpg

7、和我们想的相差甚远,那么我们就可以确定了,取值方法不对,我们之前探索方法列表以及属性列表等等都是通过调用函数读取数据,那么我们这个是否也是呢?带着一丝丝的希望,我们到cache_t结构体中翻代码,最后皇天不负有心人啊,我们能够找到如下代码:

image.png

8、那么我们再次重新运行代码,调用这个函数看看返回值是否是我们需要的呢?结果如下:

image.png

9、看上去好像是对了,但是为啥都是没有数据呢?不要忘了我们的断点是添加在了创建类的时候,这时候还没有调用过任何方法,然后我们重新打个断点,然后走流程看看结果:

image.png

10、为什么还是一样的呢?到这里了我们就用到了本篇文章开始就补充的知识哈希表,而我们这个buckets就是采用哈希表进行的存储,所以我们访问出来的数据是空的,因为它不一定说肯定会存储在第一个位置,那么我们继续看下其它位置的存储,是否如我所说的是哈希表存储呢?下面看下我的lldb调试流程:

image.png

11、经过一系列的尝试,我在第四个位置看到有数据了,但是这个数据代表着什么呢?我们怎么获取出来呢?下面我们去bucket_t中查找函数,最终找到如下几个函数:

image.png

12、我们先去尝试下sel()函数的效果,如下:

image.png

13、从以上结果说明,我们的探索流程是没有问题的,那么我们继续查找我们的sayHello方法,结果如下:

image.png

14、经过不断的尝试,我们最终找到了我们的sayHello方法,那么我们去获取下imp看看,结果如下:

image.png

411624787879_.pic.jpg

15、到这里我们通过lldb的验证就结束了,然后我们通过自己写一些相似代码,使用自定义结构体去验证一下这个缓存。

自定义代码验证cache_t

1、首先我们通过上面的流程还原出来一部分自己的代码,如下图:

image.png

2、然后我们通过运行代码看下打印效果,如图:

image.png

3、通过上述的自定义结构体,我们强制转化为自定义类型,然后打印出来cache中的内存,这也同时证明了我们的探索方向是正确的,回到开始的问题cache中存储的是对象调用的方法以及方法的实现,buckets就像一本书一样,保存了每个SELIMP对应关系。

总结

经过这一篇的文章,我们可以看到系统对于一个缓存机制的设计逻辑,对于我们开发中设置自定义的某些缓存或者其它架构打开了一个新的思路。