Runtime之方法缓存

我们都知道,在Objective-c里面,调用一个方法,其实在runtime层的时候会翻译成

     objc_msgSend(receiver, SEL)

可以想象一下,在继承关系中,一个比较深度的子类去调用父类的父类的父类... ...的方法的时候,如果没有缓存,每次都会用isa指针去挨个搜索,查找链是非常长的,如果类中的方法比较多,比较费时费力,可以看一个比较明显的例子:

1.png

在这种情况下,如果没有方法缓存,查找会变得非常耗时。
首先,先看看,方法缓存是放在哪个地方的,在类的定义中就有方法缓存,具体代码如下:
2.png

Runtime之方法缓存_第1张图片
3.png

4.png

5.png

所以,是方法缓存是根据 来的, 并不是根据具体的 类的对象来的。

方法缓存的实现可以到runtime源码中去看,为了优化性能,objc_msgSend是用汇编来实现的,在objc-msg-arm.s文件中,具体实现的步骤是:

  1. 判断receiver是否是nil。

  2. 从缓存里面寻找SEL,找到就分发,否则3。

  3. 跳转到 _objc_msgSend_uncached ,利用 _class_lookupMethodAndLoadCache3 方法(objc-class.mm中,具体可以看下面)寻找SEL

对应的代码为:

Runtime之方法缓存_第2张图片
6.png
Runtime之方法缓存_第3张图片
7.png

从代码中可以看到, 如果没有找到方法缓存,就会跳转到\_objc\_msg\_uncached这里,里面有\_class\_lookupMethodAndLoadCache3

这个函数的具体的实现如下:

根据注释可以知道:此方法可以避免再去缓存查找方法,直接去方法列表去找。

Runtime之方法缓存_第4张图片
8.png
9.png

其中:

  1. mask: 表示当前缓存能达到的最大的size,从0开始,所以total = mask + 1

  2. occupied: 表示占用的内存标志,顺便说一句,方法缓存是通过 “散列表” 的形式 实现的,散列表根据 哈希算法来定位位置,所以会产生空位,occupied用来表示已经使用的内存的个数

  3. buckets:就是用数组来表示存储缓存的散列表的存储空间,其中的每一个Method类型表示一个可用的方法缓存。

    注意:其中结构体中,最后一个成员用[1], 说明中这是一个“可变数组”,在我以前接触到的c语言中,发现有的平台是用[0]来表示,有的是用[1]来表示可变数组

具体到Method的定义:

10.png
Runtime之方法缓存_第5张图片
11.png
  1. name: 表示被缓存的方法名字

  2. types: 存储着方法的的参数类型和返回值类型

  3. imp: 就是方法的具体实现

还有,往散列表中 存方法缓存取方法缓存

  1. 存方法缓存是在objc-cache-old.mm文件中实现的,
    Runtime之方法缓存_第6张图片
    12.png
Runtime之方法缓存_第7张图片
13.png

这里就是往散列表中存储的具体实现,其中的散列查找算法是:

14.png

位置是通过sel指针偏移后和mask与后的结果得出

  1. 从缓存中取方法
    取缓存的代码是 跟objc_msgSend的实现在同一个文件中,obj-msg-arm.s中,为了查找的性能优化,也是通过汇编来实现的,方法名字是 CacheLookup,具体实现是:
Runtime之方法缓存_第8张图片
15.png
Runtime之方法缓存_第9张图片
16.png

根据查阅其中的 汇编关键字 以及注释,可以知道,取缓存和加缓存的逻辑差不多,也是根据hash去定位,如果出现冲突,根据解决hash冲突规则,继续hash, 直到找到为止,这里是 ++ 的实现形式

你可能感兴趣的:(Runtime之方法缓存)