iOS 底层原理探索 之 isa – 类的底层原理结构(中)

前言: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的不断探索之旅,希望能有帮助到各位读者朋友。
复制代码

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa – 类的底层原理结构(上)

写在前面

在之前的 iOS 底层原理探索 之 isa – 类的底层原理结构(上) 文章中,我们分析了isa的走位,以及类的继承的关系, 并且 结合isa和类的继承综合做了分析;再接着我们探索来类的底层结构,并且重点分析了class_data_bits_t bits,和其中的class_rw_t* data以及其内部的propertiesmethodsprotocolsroivars 并且验证了我们对于类方法存储在元类中的猜想是正确的。今天,我们继续探索类的底层原理结构之类的 cache

cache 缓存的意思,那么,要么缓存方法,要么缓存属性。我们根据探索bits的节奏,也是通过内存平移来一步一步探索cache。 通过上一篇的总结,我们知道,偏移到bits是32字节,偏移到cache是16字节。

准备

IMPSEL 的关系

  • SEL : 方法编号 (相当于书本中目录的名称)
  • IMP : 函数指针地址 (相当于书本中目录的页码)

首先,我们明白要找到书本中的什么内容 (SEL 目录里面的名称);然后,通过名称找对应的页码 (IMP);最后,通过页码去定位具体的内容。

cache_t 是什么数据结构

首先我们看下 cache_t的内存结构

image.png

从内存结构中并不能直接看到cache_t中的那个属性是我们想要的内容,那么,根据我们对于cache的了解, 要缓存方法或者属性,那么,一定会提供一些关于增删改查的方法,所以我们去看下cache_t所提供的方法,知道我们看到了有一个insert方法
image.png
不要犹豫,果断点进去看下实现

image.png

可以看到 insert 的过程中 对一个 bucket_t 的结构体做了操作。
接下来,我们就具体看下 bucket_t 的内容

image.png

果不其然,在bucket_t这里看到 selimp

到这一步,我们简答总结下 cache_t的数据结构:

cache_t.001.jpeg

LLDB 验证 方法的存储

接下来,我们要做一下验证,来查看下里面的内容:

image.png
image.png
那么,我们可以让 SMPerson 的实例 p 执行一下实例方法
image.png
接着,再获取一遍cache_tbuckets(),我们发现还是没有内容
image.png
那么,同样的方法,我们再去buckets_t 里面查找看看相关的方法,
果然,找到了 熟悉的 sel()imp()
image.png
那么,继续LLDB
image.png

cache_t 流程分析

首先cache_t 是一个结构体, _bucketsAndMaybeMask 是一个 uintptr_t 也就是 unsigned long, 其内部存储的是两部分数据正如其名字一样,是 bucketsmaybeMask

作为缓存,首先需要有写接着可以读。 所以 我们顺着思路找到cache_t

insert()

void insert(SEL sel, IMP imp, id receiver);
复制代码

通过方法也可以明白,插入的是sel和imp到receiver中。

image.png

INIT_CACHE_SIZE 为 4

image.png

_maybeMask.store

image.png
类中的cache在实例调用方法的时候,就开始进行存储了,首先新创建一个容器 bucket,其中的imp不是直接存储,而是将8字节的地址指针存储器中。否则,会占用很大的内存空间。在需要读取类信息的时候,就顺着这个指针地址去找寻实现。这样,不至于每次读取类信息会很慢。

image.png

所以, 此时 occupied = 1, maybeMask = 3;
image.png

buckets是一个数组,插进来的数据存储到哪里,此时,并不知道,在这里会进行cache_hash算法,这样可以得到一个哈希地址。
image.png

cache_hash

image.png

接下来就开始寻找,在sel()不存在的时候,先对occupied++, 然后在 bucket_tb 合适的位置中,将 sel imp cls() 插入进去。
如果 sel() 已经存在,那么就不在缓存。

image.png

incrementOccupied()

image.png

之后继续存储的时候,会判断是否超出了75%的容量,没有,正常存储;超出,则 判断 capacity存在后, 进行 2倍的 扩容操作(原来的 4 * 2 = 8, m = 8 – 1 = 7)。扩容后,会将,之前存储的内容一并清掉,只会有一个新存储的方法的sel imp。<重新开辟一个新的buckets, 将旧的bucket和capacity 内存地址 清空回收>

image.png

cache_fill_ratio

image.png

collect_free

image.png

为什么不直接追加,而是扩容后清空呢?

原来我们已经开辟的内存是无法改的,开辟新的内存空间后是一个新的地址,原来存储的值并不拿过来,是因为内存在数组平移的时候是十分耗费性能的,并且,苹果在底层的一个原则是越新越好。 在扩容时,之前调用的方法,再一次被使用的几率时很低的,然而,扩容之时的调用的那个方法,是很重要的,所以,系统会见只将扩容调用的那次做存储。

当然,我们可以作为探索,在扩容之后,再次调用之前的方法,发现,在cache中是会继续添加进来的;不过此时,是无序的因为上面的cache_hash算法。

最终 cache_t的流程

未命名.001.jpeg

细节

_bucketsAndMaybeMask

image.png

bucket() 取值过程中,通过 _bucketsAndMaybeMask 的指针平移才可以获取其他的buckets();

bucket()

image.png

拿到 _bucketsAndMaybeMask 的地址,然后做 操作强转成 bucket_t *

bucketsMask 在不同架构下,值是不同的
image.png

CACHE_MASK_STORAGE
image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享