探究ReactiveCocoa底层之深入了解RACSignal生
前言:自己做iOS移动开发也这么久了,相比较Swift的语法糖和新特性,OC显得异常的笨重,不管是写新的业务逻辑还是维护老项目,OC都太过于拖泥带水了,胶水代码一大堆,典型的例子就是UI层面的target-action,当项目C层代码业务复杂并且没有做解耦操作的时候,找一个按钮的点击响应事件,都要经历好几步。代码分散以及过多的胶水代码会让我们的代码业务逻辑不清晰,并且不容易维护。而上面这些问题在ReactiveCocoa函数响应式编程思想下就显得异常简单,逻辑严谨,不多废话了,让我们来深入分析一下信号的生命流程,我已经迫不及待的要开始安利给你们用了O(∩_∩)O哈!
一、深入分析信号RACSignal的生命周期
图1、信号的生命流程下面是一个最常用的信号操作,让我们结合代码来一层层分析它的实现
// 1: 创建信号/初始化信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
// 3: 发送信号
[subscriber sendNext:@"Cooci"];
// 4、: 取消订阅
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"释放了");
}];
}];
// 2: 订阅信号
[signal subscribeNext:^(id _Nullablex) {
NSLog(@"订阅:%@",x);
}];
1、创建信号
首先调用RACSignal的类方法,我们可以找到RACSignal的createSignal方法并能够看到createSignal是封装了RACDynamicSignal类的createSignal方法
+ (RACSignal*)createSignal:(RACDisposable* (^)(id subscriber))didSubscribe {
return[RACDynamicSignal createSignal:didSubscribe];
}
点击进去能看到这个类初始化一个RACDynamicSignal实例对象,这个信号量实例对象使用了函数式的编程思想把整个didSubscribe代码块都保存了下来,这里就充分体现了block代码块的优点,这块代码会一直被这个dynamicSignal所持有
+ (RACSignal*)createSignal:(RACDisposable* (^)(id subscriber))didSubscribe {
RACDynamicSignal*signal = [[self alloc]init];
// 函数式 --- 保存block 灵活 --
//初始化一个RACDynamicSignal对像并返回,同时把在创建的时候传入一个didSubscribe代码块也保存在信号里面,代码块留着以后调用,灵活度很高,此处代码块记为代码块1,方便流程分析
signal->_didSubscribe= [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
2、订阅信号
创建完成并保存了一个didSubscribe代码块之后,要想使用这个信号,就必须订阅信号,我们来看一下订阅信号里面做了什么
- (RACDisposable*)subscribeNext:(void(^)(idx))nextBlock {
NSCParameterAssert(nextBlock != NULL);
//根据传入的nextBlock,初始化一个RACSubscriber对象,目的是让RACSubscriber对象持有这个代码块,这个代码块也是留着以后调用,灵活度很高。此代码块记为代码块2
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
可以看到订阅信号,也会保存一个nextBlock代码块,并且整个操作会返回一个RACDisposable对象,后面会细说返回值RACDisposable作用,从上面这块代码我们能看出在订阅的时候传入并初始化了一个RACSubscriber对象来持有并保存nextBlock代码块,看到这里,我们不免有疑问,为什么要用block初始化一个RACSubscriber,RACSubscriber是用来干嘛的?为什么返回一个 RACDisposable对象,这个对象是用来干嘛的?请看下图:
图2、信号生命周期分析-订阅信号- (RACDisposable*)subscribe:(id)subscriber {
NSCParameterAssert(subscriber !=nil);
// dispose
//1、这里又初始化了一个RACCompoundDisposable对象,用来管理整个订阅结束及资源的清理
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
//2、这里又初始化了一个 RACPassthroughSubscriber对象,并用这个新对象同时保存了传入的subscriber(其中有订阅传入的代码块),disposable(上面一步新建的RACCompoundDisposable对象),还有self(也就是当前这个信号量RACDynamicSignal对象),此这个RACPassthroughSubscriber订阅者仅仅是将传入的三个对象整体包装了一下而已,实质起作用的还是在刚才创建的订阅者。需要注意的是,到目前为止,这个订阅传入的nextBlock和之前初始化的那个didSubscribeBlock,这两个block都只是保存了代码块暂时并没有触发!
subscriber = [[RACPassthroughSubscriber alloc]initWithSubscriber:subscriber signal:self disposable:disposable];
if (self.didSubscribe != NULL) {
//3、加入调度等等等的处理,重点来了,这里调用初始化传入的那个代码块。此处调用代码块1
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
RACDisposable*innerDisposable =self.didSubscribe(subscriber);//⚠️此处调用当前信号,初始化时候保存的那个block,那个block的调用就在这里!!!
此处的innerDisposable主要负责取消订阅后对象的清除操作,所以会有添加操作,有add肯定就有remove,对应订阅对象的添加和清除操作
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
分析以上流程不难发现,其实订阅信号的本质就是创建了一个 RACPassthroughSubscriber 类型的订阅者,并将传入的nextBlock代码块(代码块2)保存起来,留待以后调用,同时调用了第一步创建信号中保存的代码块1,并传入创建的订阅者。
3、发送信号和取消订阅
发送信号,本质上其实就是执行相应的代码块block,而在这里我们目的就是要执行订阅信号传入的那个代码块2,这个调用写在了代码块1里面,我们再回头看一下代码块1的实现部分就很清楚明了了
图3、发送信号,发送完后取消订阅二、流程及设计思想总结:
1、信号量生命流程图:
图3、信号量的生命流程2、设计思想总结:
把中间的包装层撇开不谈,RACSignal信号量底层实现无非就是在创建和订阅的同时分别保存了两个block并且在订阅的时候实现初始化保存的那个block(代码块1),然后代码块1中调用订阅信号时保存的block(代码块2),充分利用函数式和响应式思想,实现两个代码块的互相调用闭环,并且将block函数用到了极致!