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:为新的newForwardInvocationblock 函数.
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的复制体
实际上,整个调用过程就是如下图所示:
