ReactiveCocoa 谈谈concat
今天的一个业务流程,业务流程大概就是这样的
1.从CoreData中获取之前的数据
2.更新界面
3.从网络获取数据
4.判断获取结果
5.处理错误判断
6.更新界面
7.判断结果numberOfNews字段
8.现实numberOfNews信息
这种顺序行的处理,正正是ReactiveCocoa的擅长解决的问题,那么问题来了,怎么才能通过Signal,将if else 转换数据,要知道,很多地方都在block里面
这就需要用到flattenMap 和 then 这两个东西
来看看React的玩法
1 //1.从CoreData中获取数据 2 RACSignal *local = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 3 //1.1 获取完成后通知下一步 4 [subscriber sendNext:nil]; 5 [subscriber sendCompleted]; 6 return nil; 7 }]; 8 9 //2.转换数据,这个过程没有理由在mainThread中进行的 10 RACSignal *viewModel = [[local subscribeOn:[RACScheduler scheduler]] map:^id(id value) { 11 //1.2 将CoreDataModel转换成视图模型 12 return nil; 13 }]; 14 15 //3.显示到界面中 16 [viewModel subscribeNext:^(id x) { 17 18 19 }]; 20 21 //4.创建一个网络请求 22 RACSignal *request = [viewModel then:^RACSignal *{ 23 return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 24 25 NSURLSessionTask *task = nil;//这里新建一个网络请求 26 27 return [RACDisposable disposableWithBlock:^{ 28 if (task.state != NSURLSessionTaskStateCompleted) { 29 [task cancel]; 30 } 31 }]; 32 33 }]; 34 35 }]; 36 37 38 39 //5.避免重复请求,使用MutileConnection转换Signal 40 RACMulticastConnection *requestMutilConnection = [request multicast:[RACReplaySubject subject]]; 41 [requestMutilConnection connect]; 42 43 //6.处理服务器结果 44 RACSignal *response = [request flattenMap:^RACStream *(id value) { 45 //比如response中包含一个state 的枚举字段,判断这货是返回是否有效请求 46 47 // return [RACSignal return:value]; 48 return [RACSignal error:value]; 49 }]; 50 51 //7.更新界面 52 [response subscribeNext:^(id x) { 53 //再次更新界面 54 }]; 55 56 //8.处理错误 57 [response subscribeError:^(NSError *error) { 58 //处理错误 59 }];
当然,为了简化,里面留了个坑,并且省略许多逻辑代码
回到正题,concat 是 RACSignal 的一个实例方法
在源码实现如下
- (RACSignal *)concat:(RACSignal *)signal { return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ RACDisposable *concattedDisposable = [signal subscribe:subscriber]; serialDisposable.disposable = concattedDisposable; }]; serialDisposable.disposable = sourceDisposable; return serialDisposable; }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal]; }
上面的代码
1.创建一个新的信号
2.在原来的信号中订阅subscribeNext 并在completed block中将新建的Signal的subscriber传入到我们concat的信号
这里非常容易理解为什么可以在上一个信号完成时接着调用下一个信号,原因就在 signal subscribe:subscriber这里啊
但是事情并非这么简单
再看看如果使用concat 时会怎么样
一个非常简单粗暴的代码段
1 RACSignal *fristSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 2 3 NSLog(@"oneSignal createSignal"); 4 [subscriber sendNext:@""]; 5 [subscriber sendCompleted]; 6 7 return [RACDisposable disposableWithBlock:^{ 8 NSLog(@"oneSignal dispose"); 9 }]; 10 }]; 11 12 RACMulticastConnection *connection = [fristSignal multicast:[RACReplaySubject subject]]; 13 14 [connection connect]; 15 16 [connection.signal subscribeNext:^(id x) { 17 NSLog(@"2"); 18 }]; 19 20 21 RACSignal *afterConcat = [connection.signal concat:[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 22 [subscriber sendNext:@""]; 23 return nil; 24 }]]; 25 26 [afterConcat subscribeNext:^(id x) { 27 NSLog(@"afterConcat subscribeNext"); 28 }];
输出结果
2015-10-15 23:00:26.998 conatAndThen[3814:2388477] oneSignal createSignal 2015-10-15 23:00:26.999 conatAndThen[3814:2388477] oneSignal dispose 2015-10-15 23:00:27.001 conatAndThen[3814:2388477] 2 2015-10-15 23:00:27.001 conatAndThen[3814:2388477] afterConcat subscribeNext 2015-10-15 23:00:27.002 conatAndThen[3814:2388477] afterConcat subscribeNext
afterConcat 的 subscribNext被调用了两次!!!
在来看看then
1 RACSignal *fristSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 2 3 NSLog(@"oneSignal createSignal"); 4 [subscriber sendNext:@""]; 5 [subscriber sendCompleted]; 6 7 return [RACDisposable disposableWithBlock:^{ 8 NSLog(@"oneSignal dispose"); 9 }]; 10 }]; 11 12 RACMulticastConnection *connection = [fristSignal multicast:[RACReplaySubject subject]]; 13 14 [connection connect]; 15 16 [connection.signal subscribeNext:^(id x) { 17 NSLog(@"2"); 18 }]; 19 20 21 RACSignal *then = [connection.signal then:^RACSignal *{ 22 23 return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 24 [subscriber sendNext:@""]; 25 return nil; 26 }]; 27 28 }]; 29 30 [then subscribeNext:^(id x) { 31 NSLog(@"then subscribNext"); 32 }];
输出结果
2015-10-15 23:02:40.746 conatAndThen[3848:2419019] oneSignal createSignal 2015-10-15 23:02:40.747 conatAndThen[3848:2419019] oneSignal dispose 2015-10-15 23:02:40.748 conatAndThen[3848:2419019] 2 2015-10-15 23:02:40.750 conatAndThen[3848:2419019] then subscribNext
这才是我们想要的结果
then 实际上是对 concat 的包装
我们看看源码是怎么避免重复执行的
- (RACSignal *)then:(RACSignal * (^)(void))block { NSCParameterAssert(block != nil); return [[[self ignoreValues] concat:[RACSignal defer:block]] setNameWithFormat:@"[%@] -then:", self.name]; }
关键就在ignoreValues 方法中
- (RACSignal *)ignoreValues { return [[self filter:^(id _) { return NO; }] setNameWithFormat:@"[%@] -ignoreValues", self.name]; }
为了证明我的猜想,在demo中concat前filter一次
RACSignal *afterConcat = [[connection.signal filter:^BOOL(id value) { return NO; }] concat:[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@""]; return nil; }]];
结果如下
2015-10-15 23:09:51.013 conatAndThen[3967:2511660] oneSignal createSignal 2015-10-15 23:09:51.013 conatAndThen[3967:2511660] oneSignal dispose 2015-10-15 23:09:51.015 conatAndThen[3967:2511660] 2 2015-10-15 23:09:51.016 conatAndThen[3967:2511660] afterConcat subscribeNext
更深入的问题来了,为什么filter一次就可以避免重复发送
从源码拷贝出来整理分析
RACSignal *after = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; RACDisposable *sourceDisposable = [connection.signal subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{
RACDisposable *concattedDisposable = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@""];//试试把这个注释, after subscribeNext 只会执行一次 return nil; }] subscribe:subscriber]; serialDisposable.disposable = concattedDisposable; }]; serialDisposable.disposable = sourceDisposable; return serialDisposable; }]; [after subscribeNext:^(id x) { NSLog(@"afterConcat subscribeNext"); }];
真相已经出现了
在completed block 中 创建的signal(SA),其subsciber (A)已经变成了外层的Signal 的 subsciber,而 connection.signal 中 的subscribeNext 已经对(A)sendNext 一次,而我们需要concat 的signal 需要通知订阅这 在SA又sendNext一次, 所以 then 的出现就是避免 [subscriber sendNext:x]对外部执行流程的影响
参考文献
https://github.com/ReactiveCocoa/ReactiveCocoa
http://tech.meituan.com/RACSignalSubscription.html