Objc_msgSend
先回顾下objc_msgSend的流程参考之前的文章
- 判断
receiver是否为0 - 获取
receiver指向的isa - 在类的缓存中查找方法
- 缓存命中与否,缓存命中,则执行
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是用汇编写的而不是任何高级语言写的,理由如下
- 快,汇编更接近机器语言,所以在执行上速度更快,之所以有缓存也是因为要加速方法查找的流程
- 安全性
- 只能用汇编编写
- 返回类型多样化,没有任何一种高级语言支持多种返回类型
- 多种参数类型和数量
- 方法调用栈帧的处理 具体可以参考,写的比较详细
_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
x0寄存器里面存的是imp,并赋值给x17,x0第一个寄存器也是返回值的存储位置,如果imp在x0里面,必将做一件事情,就是返回,那么结果一定是在bl _lookUpImpOrForward执行后的返回值里面,也就是我们要找的imp存储的地方,所以接下来的重点看_lookUpImpOrForward.bl:b是跳转,l是链接寄存器,将下一条指令的地址保存到lr寄存器中,也就是把(mov x17, x0)的指令地址保存在lr中,当_lookUpImpOrForwar执行完以后,执行lr寄存器中的地址。_lookUpImpOrForward找到imp赋值给x17寄存器。 全局搜索lookUpImpOrForward可以看到
lookUpImpOrForward的返回类型为IMP,继续看源码,会发现
1. 先查看这个class是否已经注册
根据注释大致理解下这个函数的意思, 函数返回这个
class是不是被runtime所知晓(处于shared cache, 或者某个被load的镜像文件中的data段, 或者是被allocated with objc_allocateClassPair), 这个返回值会被缓存在class中witness这个值里面,用于fastpath的查找.
2. realize & Initialize class
对未实现或者未初始化的类进行实现或者初始化, 如下是
realize的源码
realize首先是对rw, ro的处理,之后再递归实现super class和meta class, 此时对ro, rw的处理是因为method list是存放在rw,ro中的
之后设置isa
最后attach categories
最后将所有的
methods, property, protocol全部copy 一份到rwe. 关于此方法的详细分析,会在后续的篇章中进行更新
initialize主要是调用类的initialize的方法,后续也会继续更新initialize的调用
循环查找IMP
如下是查找IMP的过程
首先找到当前
class中有没有,首先在缓存中再查找一遍,之后找super class, 如果一直找不到,则当前imp = forward_imp, 首先我们看 method 查找的主要算法
可以看到其实methods 是一个元素为method list的array, 是一个类似二位数组的结构,具体为什么这个methods设计为一个类似二维的数组结构,可以关注笔者后续的篇章. 我们看当前search_method_list_inline的实现
上述
findMethodInSortedMethodList是二分查找算法, 当找到的时候,我们会继续尝试向前查找,可以看到注释,原因是因为category可能会重写主类的方法,由此可以猜测,category的方法是存储位置相对于主类的方法是靠前的.
当我们查找到imp之后,goto done
将当前self, imp存入缓存. 具体log_and_fill_cache的分析可以参照