重温Objective-C的消息机制
消息转发机制:
- 首先在该类的缓存方法列表cache_method_list中查找,是否存在相关方法
- 上一步中若没有命中,则从方法列表 objc_method_list中查找
- 上一步中若没有命中,则从父类super的方法列表 objc_method_list中查找,直至根类NSObject
- 上一步中若没有命中,则进入消息转发流程,一共分为三步:类的动态方法解析、备用接收者对象、完整消息转发
- 动态方法解析:也就是
+(BOOL)resolveClassMethod:
方法或+(BOOL)resolveInstanceMethod:(SEL)sel
方法。该方法允许向当前对象添加方法实现。
1 |
+(BOOL)resolveInstanceMethod:(SEL)aSEL |
- 备用接收者对象:
– (id)forwardingTargetForSelector:(SEL)aSelector
方法,
该方法提供一次机会引导Objective-C RunTime 到备用接收者对象上。
1 |
-(id)forwardingTargetForSelector:(SEL)aSelector{ |
- 完整消息转发:
– (void)forwardInvocation:(NSInvocation *)anInvocation
方法,NSInvocation
是Objective-C 消息的对象形式,它包含了消息的所有信息,这也就意味着,一旦有了NSInvocation
对象,你就可以改变这个消息的所有信息,包括目标对象、selector以及参数。例如可以这样做(当然RAC与Aspect所做的事情远远不止这么简单啦):
1 |
-(void)forwardInvocation:(NSInvocation *)invocation |
rac_signalForSelector 源码走读
- (RACSignal *)rac_signalForSelector:(SEL)selector
方法位于NSObject+RACSelectorSignal 这个category下。先来看一下.h 头文件。头文件只对外暴露了以下两个方法。
1 |
- (RACSignal *)rac_signalForSelector:(SEL)selector; |
再来看一下.m 文件
1 |
|
这两方法最终都调用了C函数NSObjectRACSignalForSelector
。
- 获取Selector的别名
aliasSelector
- 是否存在关联对象,有则跳至步骤8
- 替换类
RACSwizzleClass(self)
- 获取替换的类,这一步主要是替换了原类中
forwardInvocation:
的实现。 - 创建RACSubject对象,并设置关联对象
- 获取原方法
class_getInstanceMethod(class, selector);
- 若原方法不存在,则向该类添加方法
class_addMethod(class, selector, _obj 大专栏 RAC rac_signalForSelector 如何实现对象方法的hookc_msgForward, typeEncoding)
。值得注意的是,方法体为_objc_msgForward
,即上一节中提到的完整消息转发方法的方法体。 - 若原方法存在,则向该类添加
aliasSelector
,其实现即为原方法的实现,并将原方法的实现替换为_objc_msgForward
- 返回RACSubject对象
到此为止,rac_signalForSelector 的全部工作便是将目标selector
的实现替换成了消息转发。
接下来,看看消息转发的实现部分,也就是步骤2中的实现:
1 |
|
源码很简单,就是hook了forwardInvocation:
方法,当触发完整消息转发时,首先交由RACForwardInvocation
响应,若RACForwardInvocation
响应了则结束消息转发,否则走原消息转发流程。
接下来看看RACForwardInvocation
的实现:
1 |
|
- 获取
selector
的别名aliasSelector
- 获取关联对象
subject
- 执行
aliasSelector
,并通过subject
将返回值以RACTuple的形式发送出去。
总结一下RAC实现原理:RAC利用RunTime机制将所要监听的方法,全部转发到forwardInvocation:
,并像class
添加了别名方法aliasSelector
,其方法体即原方法的方法体。那么当外部调用原方法时,就会触发消息转发流程。而RAC拦截了forwardInvocation:
,并执行别名方法aliasSelector
,最后将返回结果发送出去。
RAC在实现过程中,对Runtime的使用相当的深入。针对各种情况的考虑也是相当的周全,其实现也相当严谨,特别值得学习。
深度改造Runtime的弊端
RAC 通过深度改造对象的消息机制以达到AOP的目的,对于日常开发来说相当便利。不过值得注意的是:当一个项目内存在多个库深度改造对象的消息机制,就会产生不可避免的冲突,比如ASpect
这个库,它的实现原理有RAC完全一样,应该都是借鉴了KVO的实现方式,唯一的不同点在于ASpect
的消息forwardInvocation:
实现比RAC稍微多了一步:当对象无法响应selector时,会调用 doesNotRecognizeSelector:
抛出异常。
若同时使用这两个库对同一对象的同一方法Hook,那么该方法将无法被执行,并存在Crash隐患。
经过以上的研究,对Runtime有了更深入的了解。