Runtime消息发送和消息转发机制
方法的本质
方法的本质是消息
消息:1:消息接受者 2:消息主体
快速查找
objc_msgSend
在OC中,所有的消息调用最后都会通过 objc_msgSend 方法进行访问。通过 objc_msgSend 进行消息调用,为了加快执行速度,这个方法在runtime源码中是用汇编实现的。
调用方法的时候底层会objc_msgSend查找方法的imp
发送消息的是一个对象。如果要找类对象需要通过对象的isa指针查找到类对象。
objc_msgSend:发送消息。 isa-->消息的接受者-->类(对象方法)或元类(类方法)
万物皆对象 类也是对象 objc_class继承objc_object
isa是一个isa_t公用体,里面有很多字段。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
方法有方法缓存
方法缓存存在cache_t中,cache_t存在类对象objc_class结构体中。
在objc_class结构体中根据偏移计算相应结构体指针。
cache_t是一个结构体 里面有一个存储方法缓存的bucket_t数组,bucket_t里面有_imp,_sel。
根据sel和imp在方法缓存中,如果缓存中找不到(缓存没有命中),则来到__objc_msgSend_uncached方法中进行没有方法缓存的查找,才从methodList中找(遍历方法列表)。
总结
objc_msgSend在查找的时候先进行一个缓存的命中,通过地址的与运算,拿到当前的缓存,和当前要查找的sel进行比对,如果比对上就命中了,直接使用。没有比对上则进行__objc_msgSend_uncached。
__objc_msgSend_uncached
没有查找到缓存的时候,需要遍历方法列表。lookUpImpOrForward。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){} 从汇编层面到C++层面
慢速查找
lookUpImpOrForward
缓存没有就去找methodList,从类的data_bits中的data数据中找(data数据中有 属性列表 成员变量 方法列表 协议列表)。
lookUpImpOrForward :methodList 自己找不到 找到getSuperClass 接着循环找。
调用 lookUpImpOrForward 方法,返回值是个 IMP 指针,如果查找到了调用函数的 IMP ,则进行方法的访问
- 当前类对象的方法列表中遍历方法列表。
- 沿着当前继承链当中的superClass,指针的指向,来进行方法遍历查找。
- 一直遍历到Root Class(NSObject)
- 找到之后,就存到cache中,下次就是快速查找,不用再慢速查找了。
快速查找流程和慢速查找流程都没有找到,则进行动态方法解析
消息转发机制 动态特性
如果没有查到对于方法的 IMP 指针,则进行消息转发机制
_objc_msgForward消息转发做的几件事
1. 动态方法解析
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
拦截处理。add_method 重新给实例对象(类对象)添加方法
里面有两个方法
resolveInstanceMethod(inst, sel, cls);
resolveClassMethod(inst, sel, cls);
调用 resolveInstanceMethod:
或resolveClassMethod:
,允许用户在此时为该Class动态添加实现。如果实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。本质上是在方法列表中建立SEL和新的IMP关系。新的IMP是自己做的。如果仍没实现,继续下面的动作。
2. 消息转发流程
快速转发 慢速转发
a. 快速转发
重定向 forward(备用接收者阶段,obj_msgForward 方法的转发)找一个备用的接收者来处理消息。
如果第一层转发返回 NO ,则会进行第二层转发,调用forwardingTargetForSelector:
,可以把调用转发到另一个对象,这是类级别的转发,调用另一个类的相同的方法。
注意:这里不要返回self,否则会形成死循环。
b. 慢速转发
(标准消息转发阶段,obj_msgForward 方法的转发)爱咋咋地,消息拦截了,自己处理。
如果上面转发返回 nil ,则会进入这一层处理
这层会调用 methodSignatureForSelector:
尝试获得一个方法签名。如果获取不到则直接调用doesNotRecognizeSelector:
抛出异常。如果能获取到,则返回非nil,创建一个NSInvocation并传给forwardInvocation:
。
调用forwardInvocation:
方法,将上面获取到的方法签名包装成Invocation传入,如何处理就在这里面了,并返回nil。
通过方法签名锁定forwardInvocation 谁能处理谁处理
Invocation会绑定 target aseletor.
这次是完整的消息转发,因为你可以返回方法签名、动态指定调用方法的Target
好处:
还可以继续重定向,还可以修改方法主体。操作的可自由度高。
例:aspect 切面
c. 如果转发都失败
调用doesNotRecognizeSelector:
,crash抛出异常。
总结
_objc_msgForward在进行消息转发的过程中会涉及以下几个方法:
-
resolveInstanceMethod:(resolveClassMethod:)
add method
-
forwardingTargetForSelector:
找一个备用接受者
-
methodSignatureForSelector: 和 forwardInvocation:
完整消息转发
doesNotRecognizeSelector:
一旦调用_objc_msgForward将会跳过查找IMP的过程,直接触发消息转发。
forwardInvocation和forwardingTargetForSelector的区别
forwardingTargetForSelector只能转一个
forwardInvocation可以转多个