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基础控件 - 5.5] 代理设计模式 (基于”APP列表"练习)
A.概述 在"[iOS基础控件 - 4.4] APP列表 进一步封装,初见MVC模式”上进一步改进,给“下载”按钮加上效果.功能 1.按钮点击后,显示为“已下载”,并且不 ...
- MySQL数据库备份还原(基于binlog的增量备份)
MySQL数据库备份还原(基于binlog的增量备份) 一.简介 1.增量备份 增量备份 是指在一次全备份或上一次增量备份后,以后每次的备份只需备份与前一次相比增加或者被修改的文件.这就意味 ...
- ECSHOP在线手册布局参考图--商品分类页 category.dwt
A.购物车 1,设置方法 程序自动读取购物车的商品数量 2,代码相关 cart.lbi 中 {insert_scripts files='transport.js'} <div clas ...
- java反射快速入门(二)
上一遍博文 , 简单介绍java 反射的常用接口,本遍博文, 我会结合项目开发的实际例子讲解下 java反射的使用 现在有个需求, 要将一个对象转换成xml格式, 或者将一串xml转换一个对象, 这时 ...
- mysql 在线修改表结构工具 gh-ost
gh-ost使用测试: gh-ost -host='192.168.65.136' -user=root -password='' -database='haha' -chunk-size=10000 ...
- PJax在jQuery 3.0无法运行问题修复
PJax在jQuery 3.0无法运行 [现象] 页面报错:Uncaught TypeError: Cannot read property 'push' of undefined [原因] jQue ...
- 一个使用CDS VIEW 的 DEMO
一个使用CDS VIEW 的demo REPORT demo_cds_currency_conversion. CLASS demo DEFINITION. PUBLIC SECTION. CLASS ...
- HBase 和 MongoDB在设计上的区别
转载:http://leongfans.iteye.com/blog/1019383 昨天搜一下mongodb的资料,介绍应用的比较多,原理介绍的不多. 粗略得看了一下,总体来说两者的设计思路差不多, ...
- 64位Ubuntu配置android环境报错(...adb": error=2, 没有那个文件或目录)
Failed to get the adb version: Cannot run program "/home/jayhomzhou/android/android-sdk/platfor ...
- ubuntu安装mysql后不能远程访问的方法
ubuntu安装mysql后不能远程访问的方法1.mysql>GRANT ALL PRIVILEGES ON *.* TO 'myuser'@'%' IDENTIFIED BY 'mypassw ...