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

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. box-flex 分割

    <!DOCTYPE html> <html> <head> <style> .box{ width: 300px; height: 100px; bac ...

  2. vss2005使用

    http://www.cnblogs.com/nianyuwen/archive/2012/06/13/2547588.html 签出状态的文件别人无法使用:

  3. 委托、匿名函数、Lambda表达式和事件的学习

    委托: 还记得C++里的函数指针么?大家可以点击这里查看一下以前的笔记.C#的委托和C++中的函数指针效果一致. 当我们需要将函数作为对象进行传递和使用时就需要用到委托. 下面我们看一个例子: usi ...

  4. [转] C++ Redistributable Package版本详解

    我们使用的程序常常都需要C++ Redistributable Package的支持.C++ Redistributable Package有众多版本,给安装带了不便. 目前(2013-12-04) ...

  5. asp.net中application,cookies,stateview,session的使用

    Cookie Cookie的用法也和ASP中差不多.比如我们建立一个名为aspcn,值为飞刀的cookie HttpCookie cookie = new HttpCookie["aspcn ...

  6. .NET世界各成员之间的关系

    相信看到这篇文章的人,心中肯定有这样的想法:ODBC.OLEDB.ADO.ADO.NET貌似都是访问数据库的东东,那么他们之间有什么区别,又有什么联系呢?不要着急,待我慢慢道来. 先说ODBC,官方的 ...

  7. Nape的回调系统 nape.callbacks

    在Nape中增加一个回调大致分为三步 1:定义一些标签,并根据需求为不同的Interactor打上不同的标签 2:定义一个监听器,这个监听器定义了哪些标签触发了哪种行为之后做何种回调 3:为Space ...

  8. STL之Map的运用

    Map是c++的一个标准容器,她提供了非常好一对一的关系,在一些程序中建立一个map能够起到事半功倍的效果,总结了一些map基本简单有用的操作! 1. map最主要的构造函数:    map<s ...

  9. MySQL HA

    读写分离 在应用端处理 Spring AbstractRoutingDataSource 淘宝MyFox MySQL Replication Connection 在数据库端处理 MySQL Prox ...

  10. android学习日记03--常用控件Dialog

    常用控件 9.Dialog 我们经常会需要在Android界面上弹出一些对话框,比如询问用户或者让用户选择.这些功能我们叫它Android Dialog对话框 对话框,要创建对话框之前首先要创建Bui ...