ReactiveCocoa 谈谈concat
今天的一个业务流程,业务流程大概就是这样的
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的更多相关文章
- ReactiveCocoa 谈谈RACMulticastConnection
本文出处:http://www.cnblogs.com/forkasi/p/4886740.html 在项目里,经常会使用这种方式创建一个signal 然后next RACSignal *four = ...
- RAC & MVVM 学习资料整理
最后更新:2017-01-23 参考链接: MVVM奇葩说 MVVM 介绍 Model-View-ViewModel for iOS [译] 唐巧--被误解的 MVC 和被神化的 MVVM React ...
- ReactiveCocoa源码拆分解析(七)
(整个关于ReactiveCocoa的代码工程可以在https://github.com/qianhongqiang/QHQReactive下载) 在这篇博客中,我将把ReactiveCocoa中的擦 ...
- ReactiveCocoa源码拆分解析(二)
(整个关于ReactiveCocoa的代码工程可以在https://github.com/qianhongqiang/QHQReactive下载) 上面抽丝剥茧的把最主要的信号机制给分离开了.但在RA ...
- 最快让你上手ReactiveCocoa之进阶篇
前言 由于时间的问题,暂且只更新这么多了,后续还会持续更新本文<最快让你上手ReactiveCocoa之进阶篇>,目前只是简短的介绍了些RAC核心的一些方法,后续还需要加上MVVM+Rea ...
- 【iOS】小项目框架设计(ReactiveCocoa+MVVM+AFNetworking+FMDB)
上一个项目使用到了ReactiveCocoa+MVVM+AFNetworking+FMDB框架设计,从最初的尝试,到后来不断思考和学习,现在对这样一个整体设计还是有了一定了理解与心得.在此与大家分享下 ...
- ReactiveCocoa学习总结
最近一直断断续续学习关于ReactiveCocoa的知识内容,对于它的一些基础内容将通过本文进行一个总结,主要是一些入门知识 一:RACSignal一些运用 @interface RACSignalT ...
- ReactiveCocoa基础知识内容
本文记录一些关于学习ReactiveCocoa基础知识内容,对于ReactiveCocoa相关的概念如果不了解可以网上搜索:RACSignal有很多方法可以来订阅不同的事件类型,ReactiveCoc ...
- ReactiveCocoa常见操作方法介绍/MVVM架构思想
1.ReactiveCocoa常见操作方法介绍. 1.1 ReactiveCocoa操作须知 所有的信号(RACSignal)都可以进行操作处理,因为所有操作方法都定义在RACStream.h中, ...
随机推荐
- IOS-- UIView中的坐标转换
// 将像素point由point所在视图转换到目标视图view中,返回在目标视图view中的像素值 - (CGPoint)convertPoint:(CGPoint)point toView:(UI ...
- CSS layout入门
元素与盒 在HTML中常常使用的概念是元素,而在CSS中,布局的基本单位是盒,盒总是矩形的. 元素与盒并非一一对应的关系,一个元素可能生成多个盒,CSS规则中的伪元素也可能生成盒,display属性为 ...
- Resharp非常实用的快捷键
Alt+Home 定位到父类.父接口 Alt + End 定位到子类 Ctrl+T 快速在整个解决方案下搜索 类型.方法.文件夹 Alt+Ctrl+Spance 给出提示框 Shift+F ...
- Winform- DotNetBar for Windows Forms的安装的添加
Winform界面不好看,偶尔在网上看到winform的界面美化,对比了一下选择了DotNetBar for Windows Forms 1.破解版网上很多,提供一个参考的下载地址http://dx. ...
- MYSQL- 分页存储过程
工作需要,用到MYSQL的分页功能,在网上找到一个不错的分页存储过程,代码整理了一下! 存储过程代码 CREATE PROCEDURE `sp_hj_splitpage`( in _pagecurre ...
- 在idea中如何添加log日志
1.首先下载log4的jar包,官方路径为:http://www.apache.org/dyn/closer.cgi/logging/log4j/1.2.17/log4j-1.2.17.zip 2.下 ...
- 【转】linux中的cut/tr/join/split/xargs命令
1. cut命令 cut命令用于从文件或者标准输入中读取内容并截取每一行的特定部分并送到标准输出. 截取的方式有三种:一是按照字符位置,二是按照字节位置,三是使用一个分隔符将一行分割成多个field, ...
- mysqldump原理3
现网中数据库运维时,要经常对数据库做热备.为保证恢复时数据的完整性与一致性, 一种方法是在备份之前锁表,但锁表会影响正在运行的业务. mysqldump是当前MySQL中最常用的备份工具,通过mysq ...
- oracle数据库管理员简介、导入数据与导出数据
数据库管理员: sys和system的权限区别:sys:所有oracle的数据字典的基表和视图都存放在sys用户中,这些基表和视图对于oracle的运行时至关重要的,由数据库 自己维护,任何用户都不能 ...
- Android_gridView_LIstener_examle
layout.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" x ...