RecativeCoCoa objectivec 版本的分析,第一部分主要阐述
rac_signalForSelector:
该方法的实现,观摩一下 RAC 是如何监听方法回调的.
rac_signalForSelector
当我们监听某个变量的变化,很自然我们就想到了 KVO 的方式, 但是我们有想过监听方法吗?在 RAC 当中,我们需要监听某个方法的被调用,就使用到了rac_signalForSelector
这个函数。先看一下这个函数的实现方法。
1 | // NSobject+RACSelectorSiganl.m |
方法 rac_signalForSelector
直接调用了静态函数 NSObjectRACSignalForSelector
!
NSObjectRACSignalForSelector
1 | // NSobject+RACSelectorSiganl.m |
总体上方法做了以下这些步骤:
SEL aliasSelector = RACAliasForSelector(selector);
这个方法得到了字符串拼接rac_alias_ + @selector
后的方法aliasSelector
用于后面替换原方法,aliasSelector
实际上是被监听方法selector
的复制体,因为下面步骤会将监听方法selector
替换,所以这里首先要保存一下。RACSubject *subject
为热信号,检测是否已经在监听改方法,如果有,把信号返回。(关于冷热信号的概念可以查看美团的细说ReactiveCocoa的冷信号与热信号(三):怎么处理冷信号与热信号)- RACSwizzleClass 动态创建好
NSObject* Self
对象的 RAC 关联类, 这个关联类是继承自NSObject* Self
的isa
的,类名为class + _RACSelectorSignal
,并把这个映射类的符号添加到 OC 动态类符号当中,然后让对象NSObject* Self
的isa
指向这个映射类, 然后将 RAC 关联类中的方法转发forwardInvocation:
,完成方法监听. - 创建热信号
RACSubject
, 并跟aliasSelector
方法设置为映射关系,方便我们后面直接通过映射获取RACSubject
来发送信号 - 先查看一下对象 object 是否存在被监听的实例方法,如果不存在而查看被监听方法为协议方法
- 通过
class_addMethod
为关联类增添监听方法selector
的复制体aliasSelector
, 这样才能方便后面能后调用监听方法selector
class_replaceMethod
方法把参数selector
方法替换成 OC消息转发方法_objc_msgForward
,由于selector
方法被替换了,掉用的时候自然都会调用forwardInvocation:
方法了,但是selector
的实现也消失了,不过我们之前已将创建了复制体——aliasSelector
,难道不是吗?
结合NSObject文档可以知道,_objc_msgForward
消息转发做了如下几件事:1.调用resolveInstanceMethod:方法,允许用户在此时为该Class动态添加实现。如果有实现了,则调用并返回。如果仍没实现,继续下面的动作。
2.调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。如果获取到,则直接转发给它。如果返回了nil,继续下面的动作。
3.调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。
4.调用forwardInvocation:方法,将地3步获取到的方法签名包装成Invocation传入,如何处理就在这里面了。
通过上面这幅图,可以看出,如果一个 OC 方法在类符号中查找不到的时候,就进行了 _objc_msgForward
,而 _objc_msgForward
到最后都会调用到 -forwardInvocation
这个 OC 方法,那么RAC的意图就显然易见了,它需要做的,就是利用方法调用时,将所有被监听的方法都运行到-forwardInvocation
,通过改造-forwardInvocation
方法,利用 RAC 的信号,通知外层监听实现方法。
RACSwizzleClass构建映射类
1 | static Class RACSwizzleClass(NSObject *self) |
object.class
由于KVO重写了class 方法,所以不能准确的找到类.object_getClass()
方法可以准确的找到 isa 指针.object.class
与object_getClass(object)
进行判断 来防止KVO导致的AOP无效.
为什么会发生这种情况?
因为当你使用 KVO 的时候,系统会帮你重写类的 class 方法,生成NSKVONotifying_ + Class
。例如如下代码1
2
3
4
5
6
7
8[self.view addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context {
Class statedClass = self.view.class;
Class baseClass = object_getClass(self.view);
NSLog(@"%@ %@", NSStringFromClass(statedClass), NSStringFromClass(baseClass));
}
得到的 log:
RACLearning[3657:478602] UIView NSKVONotifying_UIView
swizzledClasses()
专门用来存储 isa 被修改过的类,这些类不用重新生成为 RAC 的类.- 检查OC类符号表中是否存在经过RAC改造的类 ——
Class + _RACSelectorSignal
。 - 不存在改造类的情况下,利用运行时方法
objc_allocateClassPair
创建继承Class类的类符号——Class + _RACSelectorSignal
。 - RACSwizzleForwardInvocation 利用运行时替换方式
class_replaceMethod
将经过改造的类Class + _RACSelectorSignal
的消息转发的方法forwardInvocation:
的实现替换成 RAC 的新实现. - 由于我们在方法NSObjectRACSignalForSelector将被监听方法
selector
替换成了_objc_msgForward
函数了,所以当我们使用外层API调用respondsToSelector
去判断selector
是否有实现的时候,很明显会返回false, 而RACSwizzleRespondsToSelector 将经过改造的类Class + _RACSelectorSignal
的respondsToSelector:
方法实现转而判断aliasSelector
是否实现的前提了,前文也说过aliasSelector
实际上是被监听方法selector
的复制体。 - RACSwizzleGetClass让 RAC 让新创建的类
Class + _RACSelectorSignal
的class
方法的实现全部返回对象NSObject* self
的类,也就是类Class + _RACSelectorSignal
变成了一个真正的伪装类,为了让对象对自己的身份『说谎』,所有的方法都转发到了这个子类上,如果不修改 class 方法,那么当开发者使用它自省时就会得到错误的类,而这是我们不希望看到的 - 通过上面图中可以知道,必须为
forwardInvocation:
提供一个方法签名,才能走运行时转发,所以RACSwizzleMethodSignatureForSelector自然是为类Class + _RACSelectorSignal
创建方法签名了. - 将对象
NSObject *self
的isa强制指向经过RAC改造的类 ——Class + _RACSelectorSignal
了,这些下来左右调用该对象的所有方法都会访问Class + _RACSelectorSignal
类结构体中的MethodList
.
RACSwizzleForwardInvocation
1 | static void RACSwizzleForwardInvocation(Class class) { |
- 定义 IMP 函数指针指针
originalForwardInvocation
,指向class
类的forwardInvocation
内部函数实现, - 然后创建新的
forwardInvocation:
block 函数newForwardInvocation
, block 函数内部先调用 RACForwardInvocation 函数,以调用映射过后的rac_alias_ + @selector
函数(实际上是@selector
的复制体),然后给热信号subject
发送信号 Next 以调用订阅 blocknexblock
. - 利用
class_replaceMethod
方法替换了 class 的对象方法forwardInvocation:
为新的newForwardInvocation
block 函数.
RACForwardInvocation
1 | static BOOL RACForwardInvocation(id self, NSInvocation *invocation) { |
由 NSObjectRACSignalForSelector 中知道,热信号 subject 已经被创建了, 而且利用了 class_addMethod
方法完成了被监听方法 @selector
的复制体 rac_alias_ + @selector
方法,所以这里直接利用 [invocation invoke]
调用 rac_alias_ + @selector
方法,然后再像热信号 subject
发送 next
信号并且带上 rac_alias_ + @selector
方法的参数数组rac_argumentsTuple
以调用 nextblock.
rac_argumentsTuple
1 | - (RACTuple *)rac_argumentsTuple { |
- 参数个数
methodSignature.numberOfArguments
默认有一个 _cmd 一个 target 所以要 -2 - 获取该方法的参数 ,
rac_argumentAtIndex
函数内通过 methodSignature 的getArgumentTypeAtIndex
方法来判断获取 OC 对象或 基础类型(如 int,char,bool 等)转成的 NSNumber 对象。 ?:
新写法,如果有则添加返回的 OC 对象,没有就把RACTupleNil.tupleNil
单例对象添加进去(为什么添加单例对象?可以节省内存!)- 利用
RACTuple
对象吧参数数组包装一下返回。
RACSwizzleRespondsToSelector
1 | static void RACSwizzleRespondsToSelector(Class class) { |
更换改造类class + _RACSelectorSignal
的respondsToSelector
方法,让aliasSelector
方法成为真正的被监听方法selector
的复制体
实际上,整个调用过程就是如下图所示: