今天的一个业务流程,业务流程大概就是这样的

1.从CoreData中获取之前的数据

2.更新界面

3.从网络获取数据

4.判断获取结果

5.处理错误判断

6.更新界面

7.判断结果numberOfNews字段

8.现实numberOfNews信息

这种顺序行的处理,正正是ReactiveCocoa的擅长解决的问题,那么问题来了,怎么才能通过Signal,将if else 转换数据,要知道,很多地方都在block里面

这就需要用到flattenMap 和 then 这两个东西

来看看React的玩法

  //1.从CoreData中获取数据
RACSignal *local = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//1.1 获取完成后通知下一步
[subscriber sendNext:nil];
[subscriber sendCompleted];
return nil;
}]; //2.转换数据,这个过程没有理由在mainThread中进行的
RACSignal *viewModel = [[local subscribeOn:[RACScheduler scheduler]] map:^id(id value) {
//1.2 将CoreDataModel转换成视图模型
return nil;
}]; //3.显示到界面中
[viewModel subscribeNext:^(id x) { }]; //4.创建一个网络请求
RACSignal *request = [viewModel then:^RACSignal *{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSURLSessionTask *task = nil;//这里新建一个网络请求 return [RACDisposable disposableWithBlock:^{
if (task.state != NSURLSessionTaskStateCompleted) {
[task cancel];
}
}]; }]; }]; //5.避免重复请求,使用MutileConnection转换Signal
RACMulticastConnection *requestMutilConnection = [request multicast:[RACReplaySubject subject]];
[requestMutilConnection connect]; //6.处理服务器结果
RACSignal *response = [request flattenMap:^RACStream *(id value) {
//比如response中包含一个state 的枚举字段,判断这货是返回是否有效请求 // return [RACSignal return:value];
return [RACSignal error:value];
}]; //7.更新界面
[response subscribeNext:^(id x) {
//再次更新界面
}]; //8.处理错误
[response subscribeError:^(NSError *error) {
//处理错误
}];

当然,为了简化,里面留了个坑,并且省略许多逻辑代码

回到正题,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 时会怎么样

一个非常简单粗暴的代码段

     RACSignal *fristSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

         NSLog(@"oneSignal createSignal");
[subscriber sendNext:@""];
[subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{
NSLog(@"oneSignal dispose");
}];
}]; RACMulticastConnection *connection = [fristSignal multicast:[RACReplaySubject subject]]; [connection connect]; [connection.signal subscribeNext:^(id x) {
NSLog(@"");
}]; RACSignal *afterConcat = [connection.signal concat:[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@""];
return nil;
}]]; [afterConcat subscribeNext:^(id x) {
NSLog(@"afterConcat subscribeNext");
}];

输出结果

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

     RACSignal *fristSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

         NSLog(@"oneSignal createSignal");
[subscriber sendNext:@""];
[subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{
NSLog(@"oneSignal dispose");
}];
}]; RACMulticastConnection *connection = [fristSignal multicast:[RACReplaySubject subject]]; [connection connect]; [connection.signal subscribeNext:^(id x) {
NSLog(@"");
}]; RACSignal *then = [connection.signal then:^RACSignal *{ return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@""];
return nil;
}]; }]; [then subscribeNext:^(id x) {
NSLog(@"then subscribNext");
}];

输出结果

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

ReactiveCocoa 谈谈concat的更多相关文章

  1. ReactiveCocoa 谈谈RACMulticastConnection

    本文出处:http://www.cnblogs.com/forkasi/p/4886740.html 在项目里,经常会使用这种方式创建一个signal 然后next RACSignal *four = ...

  2. RAC & MVVM 学习资料整理

    最后更新:2017-01-23 参考链接: MVVM奇葩说 MVVM 介绍 Model-View-ViewModel for iOS [译] 唐巧--被误解的 MVC 和被神化的 MVVM React ...

  3. ReactiveCocoa源码拆分解析(七)

    (整个关于ReactiveCocoa的代码工程可以在https://github.com/qianhongqiang/QHQReactive下载) 在这篇博客中,我将把ReactiveCocoa中的擦 ...

  4. ReactiveCocoa源码拆分解析(二)

    (整个关于ReactiveCocoa的代码工程可以在https://github.com/qianhongqiang/QHQReactive下载) 上面抽丝剥茧的把最主要的信号机制给分离开了.但在RA ...

  5. 最快让你上手ReactiveCocoa之进阶篇

    前言 由于时间的问题,暂且只更新这么多了,后续还会持续更新本文<最快让你上手ReactiveCocoa之进阶篇>,目前只是简短的介绍了些RAC核心的一些方法,后续还需要加上MVVM+Rea ...

  6. 【iOS】小项目框架设计(ReactiveCocoa+MVVM+AFNetworking+FMDB)

    上一个项目使用到了ReactiveCocoa+MVVM+AFNetworking+FMDB框架设计,从最初的尝试,到后来不断思考和学习,现在对这样一个整体设计还是有了一定了理解与心得.在此与大家分享下 ...

  7. ReactiveCocoa学习总结

    最近一直断断续续学习关于ReactiveCocoa的知识内容,对于它的一些基础内容将通过本文进行一个总结,主要是一些入门知识 一:RACSignal一些运用 @interface RACSignalT ...

  8. ReactiveCocoa基础知识内容

    本文记录一些关于学习ReactiveCocoa基础知识内容,对于ReactiveCocoa相关的概念如果不了解可以网上搜索:RACSignal有很多方法可以来订阅不同的事件类型,ReactiveCoc ...

  9. ReactiveCocoa常见操作方法介绍/MVVM架构思想

      1.ReactiveCocoa常见操作方法介绍. 1.1 ReactiveCocoa操作须知 所有的信号(RACSignal)都可以进行操作处理,因为所有操作方法都定义在RACStream.h中, ...

随机推荐

  1. Java输出考试前三名

    实现输出考试成绩的前三名 要求: 1. 考试成绩已保存在数组 scores 中,数组元素依次为 89 , -23 , 64 , 91 , 119 , 52 , 73 2. 要求通过自定义方法来实现成绩 ...

  2. fastjson使用示例

    一.fastjson 一款Json处理工具包,包括“序列化”和“反序列化”两部分,它具备如下特征:速度最快,测试表明,fastjson具有极快的性能,超越任其他的Java Json parser.包括 ...

  3. 使用openssl库实现des,3des加密

    原文地址: 使用openssl库实现des,3des加密 主要是调整了一下格式,以及一些变量的类型,以解决在VC2008下无法编译通过的问题. #include <stdio.h> #in ...

  4. iOS 有关自动轮播图片

    //初始化当前视图 _currentImageView = [[UIImageView alloc] init]; [_currentImageView setImageWithURL:[NSURL ...

  5. Jfinal极速开发微信系列教程(一)--------------Jfinal_weixin demo的使用分析

    概述: Jfinal_weixin已经出了有好一段时间了!一直在关注当中......最近工作上有需要到这个东西,所以,话了两个小时来看看这个东西,看完demo以后,豁然开朗,原理微信和一般的web项目 ...

  6. 【转】如何从零开始学会 Ruby on Rails?

    文章转自:http://huacnlee.com/blog/how-to-start-learning-ruby-on-rails/ 这个话题曾经给身边的很多朋友说过同样的话题,这里整理以下. 如果你 ...

  7. MySQL Cluster测试过程中的错误汇总--ERROR 1296 (HY000)等等

    参考资料: http://dev.mysql.com/doc/refman/5.1/en/mysql-cluster-privilege-distribution.html http://www.cl ...

  8. [Effective C++ --022]将成员变量声明为private

    这一章并没有什么太多的内容,作者无非想告诉我们一件事:成员变量应该是private. 为此,他列举了以下的理由: 1.成员函数来返回成员变量是非常高效: 2.protected成员变量并不比publi ...

  9. [Effective C++ --016]成对使用New和Delete时要采用相同形式

    这一节比较简单,可以总结为以下: std::string *stringPtr1 = new std::string; std::]; .. delete stringPtr1; // delete ...

  10. [008]new、delete及动态内存分配

    1.new和delete都会用,这里只声明一点: C++ 没有明确定义如何释放指向不是用 new 分配的内存地址的指针. 比如下面的代码: #include<iostream> using ...