慢速方法查找

717 阅读4分钟

Objc_msgSend

先回顾下objc_msgSend的流程参考之前的文章

  1. 判断receiver是否为0
  2. 获取receiver指向的isa
  3. 在类的缓存中查找方法
  4. 缓存命中与否,缓存命中,则执行imp,没有命中,则执行到_objc_msgSend_uncached慢速查找流程 如果调用[super ***]则调起的是objc_msgSendSuper, receier是一样的. 例子:
[self class] 和 [super class] 的输出是一样的,因为objc_msgSend 还是 objec_msgSendSuper 的 传入的receiver 都是一样的

self 指的是当前传入的receiver, super是关键字

struct objc_super {
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
    /* super_class is the first class to search */
};
//最终super 的调用如下
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

objc_msgSendsuper的第一个参数是objc_super,只是我们查找方法是从super class开始查找的。 关于为什么objc_msgSend是用汇编写的而不是任何高级语言写的,理由如下

  1. 快,汇编更接近机器语言,所以在执行上速度更快,之所以有缓存也是因为要加速方法查找的流程
  2. 安全性
  3. 只能用汇编编写
    1. 返回类型多样化,没有任何一种高级语言支持多种返回类型
    2. 多种参数类型和数量
    3. 方法调用栈帧的处理 具体可以参考,写的比较详细

_lookUpImpOrForward

在汇编的快速查找没有找到缓存,就会进入_objc_msgSend_uncached,在_objc_msgSend_uncached 里面最主要的是对MethodTableLookup的处理。如下是MethodTableLookup的代码

.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
// IMP in x0 --
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro

  1. x0寄存器里面存的是imp,并赋值给x17 x0第一个寄存器也是返回值的存储位置,如果impx0里面,必将做一件事情,就是返回,那么结果一定是在bl _lookUpImpOrForward执行后的返回值里面,也就是我们要找的imp存储的地方,所以接下来的重点看_lookUpImpOrForward.
  2. blb是跳转,l是链接寄存器,将下一条指令的地址保存到lr寄存器中,也就是把(mov x17, x0)的指令地址保存在lr中,当_lookUpImpOrForwar执行完以后,执行lr寄存器中的地址。_lookUpImpOrForward找到imp赋值给x17寄存器。 全局搜索lookUpImpOrForward image.png 可以看到lookUpImpOrForward的返回类型为IMP,继续看源码,会发现

1. 先查看这个class是否已经注册

image.png image.png 根据注释大致理解下这个函数的意思, 函数返回这个class是不是被runtime所知晓(处于shared cache, 或者某个被load的镜像文件中的data段, 或者是被allocated with objc_allocateClassPair), 这个返回值会被缓存在classwitness这个值里面,用于fastpath的查找.

2. realize & Initialize class

image.png image.png 对未实现或者未初始化的类进行实现或者初始化, 如下是realize的源码 image.png

realize首先是对rw, ro的处理,之后再递归实现super classmeta class, 此时对ro, rw的处理是因为method list是存放在rw,ro中的 image.png

之后设置isa

image.png

最后attach categories image.png image.png 最后将所有的methods, property, protocol全部copy 一份到rwe. 关于此方法的详细分析,会在后续的篇章中进行更新

initialize主要是调用类的initialize的方法,后续也会继续更新initialize的调用

循环查找IMP

如下是查找IMP的过程 image.png 首先找到当前class中有没有,首先在缓存中再查找一遍,之后找super class, 如果一直找不到,则当前imp = forward_imp, 首先我们看 method 查找的主要算法 image.png

可以看到其实methods 是一个元素为method listarray, 是一个类似二位数组的结构,具体为什么这个methods设计为一个类似二维的数组结构,可以关注笔者后续的篇章. 我们看当前search_method_list_inline的实现 image.png image.png 上述findMethodInSortedMethodList是二分查找算法, 当找到的时候,我们会继续尝试向前查找,可以看到注释,原因是因为category可能会重写主类的方法,由此可以猜测,category的方法是存储位置相对于主类的方法是靠前的.

当我们查找到imp之后,goto done image.png

将当前self, imp存入缓存. 具体log_and_fill_cache的分析可以参照