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

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. define定义方法

    define('a',function(){ ; return x; }); define('b',['a'],function(a){ ; return y+a; }); M.use(['b'],f ...

  2. UVa 二分图匹配 Examples

    这些都是刘汝佳的算法训练指南上的例题,基本包括了常见的几种二分图匹配的算法. 二分图是这样一个图,顶点分成两个不相交的集合X , Y中,其中同一个集合中没有边,所有的边关联在两个集合中. 给定一个二分 ...

  3. UITableView 详解 教程

    看TableView的资料其实已经蛮久了,一直想写点儿东西,却总是因为各种原因拖延,今天晚上有时间静下心来记录一些最近学习的TableView的知识.下面进入正题,UITableView堪称UIKit ...

  4. 好的git教程

    1.GitHub使用教程for VS2012 http://www.cnblogs.com/yc-755909659/p/3753355.html

  5. home_work--用户登陆

    前提: 根据文本中的已有用户和密码,实现登陆的交互:在用户输入用户名后,会有一个检测用户是否被锁定的环节:如果用户三次密码输入错误,则该用户则会被添加到锁定文件中,无法登陆. 涉及到的知识点: 1.p ...

  6. DNS服务未响应的简单解决办法

    今天晚上下班回家,打开电脑,发现打不开网页了,同一个wifi环境下,我的手机是可以连接上的,网上搜了一大推,又是重启服务,又是重新填写dns服务地址,都不管用, 该怎么办呢??. 其实发现很简单,打开 ...

  7. ECSHOP模板文件说明

    最新ECSHOP 2.7.3完整的一套ECSHOP模板有一下构造 • 35个 .dwt文件(模板框架文件.可调用lbi库文件的主体文件) • 57个 .lbi文件(模板库文件,可通过后台库项目管理直接 ...

  8. [置顶] 【玩转cocos2d-x之七】场景类CCScene和布景类CCLayer

    原创作品,转载请标明:http://blog.csdn.net/jackystudio/article/details/12708811 场景类CCScene和布景类CCLayer都是作为一个容器来使 ...

  9. C#_MVC 自定义AuthorizeAttribute实现权限管理

    随笔- 28  文章- 31  评论- 16 MVC 自定义AuthorizeAttribute实现权限管理   在上一节中提到可以使用AuthorizeAttribute进行权限管理: [Autho ...

  10. CCS5 建立SYS/BIOS工程时报错“cannot find file "./configPkg/linker.cmd" bios”的解决方法

    CCS5 建立SYS/BIOS工程时报错“cannot find file "./configPkg/linker.cmd" bios”的解决方法 报错 #10008-D cann ...