Fork me on GitHub

探究 ReactiveObjC(RAC) 中 -rac_signalForSelector: 的实现

一直觉得 rac_signalForSelector 十分好用,但是并不了解它的内部机制。趁着有空,查看一下原码,探究 ReactiveObjC(RAC) 中 -rac_signalForSelector: 的实现。

1
2
3
4
5
- (RACSignal *)rac_signalForSelector:(SEL)selector {
NSCParameterAssert(selector != NULL);

return NSObjectRACSignalForSelector(self, selector, NULL);
}

 

rac_signalForSelector 方法内部只调用了一个函数,继续跟进来查看 NSObjectRACSignalForSelector 的函数实现:

image-20181016175429913

 

函数内首先调用的是一个名为 RACSwizzleClass 的函数,继续跟进可以发现,在 RACSwizzleClass() 这个函数中,做了类似于 KVO 的处理——创建当前对象所属类的子类,并将当前对象的类对象指向子类对象。而且,如果检测到该对象已经做了 KVO 监听(已经动态创建了子类),RAC 会做兼容处理。

image-20181016172717946

 

RACSwizzleClass() 函数中创建了子类,并且交换了几个关键方法,并在返回值中将创建的子类返回出来(函数中还做了一些缓存相关的操作,这里只研究主流程)。这里隐藏了一个关键的方法交换:RACSwizzleForwardInvocation() 函数交换了对象的消息转发方法,也就意味着,之后一旦有消息转发,RAC 会进行拦截。

这里多说一句,动态创建的类对象存储在堆区 而不是数据段

1
2
3
4
5
6
7
// 断点调试得到通过 Runtime 动态创建的类对象 内存地址
subclass Class 0x6000037502a0
// 断点下通过 lldb 命令新建一个堆对象,查看其内存地址
(lldb) po [NSObject new]
<NSObject: 0x600003b171c0>

// 可以得出,通过 Runtime 动态创建的类对象信息,是存在于内存的 堆区 而非 数据段

 

接下来,RAC 交换了 -dealloc 方法,并在 -dealloc 方法中,执行一些销毁操作。

image-20181016175557900

 

然后,关键方法来了。

image-20181016180158571

 

RAC 将要监听的方法替换成了消息转发,也就是说这个方法被调用后将跳过消息发送和动态方法解析的阶段自动进入消息转发阶段,然后消息转发中 RAC 早已做了拦截处理,发现调用的方法被监听之后先执行原来的方法实现,然后给每个监听者发信号,执行监听的 Block,这样就实现了方法调用时的监听操作。

总结

其实 RAC 在 -rac_signalForSelector: 的实现中有 3个关键步骤:

1、RACSwizzleClass() 函数中类似于 KVO 的子类化操作,以及对 KVO、本类中其他的监听进行兼容。

2、RACSwizzleClass() 中执行 RACSwizzleForwardInvocation() 函数,拦截并处理相关消息转发,发现是经过 -rac_signalForSelector:监听的方法,给所有的监听发出信号。

3、NSObjectRACSignalForSelector() 函数中使用 class_replaceMethod() 函数将 -rac_signalForSelector: 消息实现转为 消息转发,然后在上一步交换的消息转发方法中进行拦截处理。

 

RAC 内部实现了一个完整的流程,在对象 -dealloc 方法中,会释放所有监听的 block,所以我们也不必担心内存问题,只要注意在 Block 中适当的传入 weak 指针,其余放心使用即可。

------------- 本文结束感谢您的阅读 -------------