源码结构分析
我们先来看一下OC类的最新结构定义:
|
|
之前的文章中我们分析了bits这个属性,里面存放的就是类的子结构,比如方法,属性,协议等。今天我们再来探究下上面的cache内部的结构和底层原理。
实操
相信大家对cache都不会陌生,即OC中的方法缓存,在objc_msgSend的流程中,最先查找的就是这个列表,那OC是如何维护这个列表的呢,内部的存储结构又是如何?今天我们就来一探究竟。
之前我们使用lldb调试了bits内部的存储结构,但是这个方式比较繁琐,今天我们换一种简单点的方式来Debug,我们首先镜像一个objc_class结构体,然后对镜像的结构体进行分析。
|
|
首先我们不调用任何实例方法,查看打印结果
|
|
可以看到,初始状态都是0,里面的for也没有进 说明当前缓存列表为空。
然后我们分次调用方法并打印:
|
|
查看打印结果如下:
|
|
分析打印结果,我们存在几个疑惑的地方:
- _occupied 和 _mask 分别是什么,为什么调用方法会改变它们的值?
- 方法的调用顺序和缓存列表的顺序为什么不一致?
- 当我们调用后面的方法后,前面缓存的方法为什么会丢失?
带着上面的问题,我们去源码中看看能不能找到满意的答案。
源码逻辑分析
通过对occupied关键字的搜索,我们最终定位到下面这个函数:
|
|
这又是一个内联函数,意味这你在调用堆栈里面是看不到的。但是我们通过源码还是可以Debug进来的。
通过以上源码的分析过程,应该能够回答我们上面的疑问了。
- _occupied表示当前已缓存的的方法数,_mask标识当前缓存列表最大数-1,有新的方法调用_occupied就会更新,缓存列表扩容时_mask会更新。
- 存储到缓存列表中的方法并不一定是连续的,和具体的hash算法有关。
- 当缓存列表扩容后,之前缓存过的方法都会被清除,所以会丢失。
小结
当前的探索因为工程比较小,可能还不能看出方法cache的好处,在实际的工程中,方法的调用是大量且频繁的,这时就能体现出方法缓存的实际意义。之前的学习中对方法缓存丢失的逻辑不太了解,经过这次的探索,对整个方法缓存的策略有了更深刻的理解。