[转]使用ReactiveCocoa实现iOS平台响应式编程
原文:http://www.itiger.me/?p=38
使用ReactiveCocoa实现iOS平台响应式编程
ReactiveCocoa和响应式编程
在说ReactiveCocoa之前,先要介绍一下FRP(Functional Reactive Programming,响应式编程),在维基百科中有这样一个例子介绍:
在命令式编程环境中,a = b + c 表示将表达式的结果赋给a,而之后改变b或c的值不会影响a。但在响应式编程中,a的值会随着b或c的更新而更新。
Excel就是响应式编程的一个例子。单元格可以包含字面值或类似”=B1+C1″的公式,而包含公式的单元格的值会依据其他单元格的值的变化而变化 。
而ReactiveCocoa简称RAC,就是基于响应式编程思想的Objective-C实践,它是Github的一个开源项目,你可以在这里找到它。
关于FRP和ReactiveCocoa可以去看leezhong的这篇blog,图文并茂,讲的很好。
ReactiveCocoa框架概览
先来看一下leezhong再博文中提到的比喻,让你对有个ReactiveCocoa很好的理解:
可以把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。可以在水龙头上加一个过滤嘴(filter),不符合的不让通过,也可以加一个改动装置,把球改变成符合自己的需求(map)。也可以把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。
下面我来逐一介绍ReactiveCocoa框架的每个组件
Streams
Streams 表现为RACStream类,可以看做是水管里面流动的一系列玻璃球,它们有顺序的依次通过,在第一个玻璃球没有到达之前,你没法获得第二个玻璃球。
RACStream描述的就是这种线性流动玻璃球的形态,比较抽象,它本身的使用意义并不很大,一般会以signals或者sequences等这些更高层次的表现形态代替。
Signals
Signals 表现为RACSignal类,就是前面提到水龙头,ReactiveCocoa的核心概念就是Signal,它一般表示未来要到达的值,想象玻璃球一个个从水龙头里出来,只有了接收方(subscriber)才能获取到这些玻璃球(value)。
Signal会发送下面三种事件给它的接受方(subscriber),想象成水龙头有个指示灯来汇报它的工作状态,接受方通过-subscribeNext:error:completed:对不同事件作出相应反应
- next 从水龙头里流出的新玻璃球(value)
- error 获取新的玻璃球发生了错误,一般要发送一个NSError对象,表明哪里错了
- completed 全部玻璃球已经顺利抵达,没有更多的玻璃球加入了
一个生命周期的Signal可以发送任意多个“next”事件,和一个“error”或者“completed”事件(当然“error”和“completed”只可能出现一种)
Subjects
subjects 表现为RACSubject类,可以认为是“可变的(mutable)”信号/自定义信号,它是嫁接非RAC代码到Signals世界的桥梁,很有用。嗯。。。 这样讲还是很抽象,举个例子吧:
|
1
2
3
|
RACSubject *letters=[RACSubject subject];
RACSignal *signal=[letters sendNext:@"a"];
|
可以看到@"a"只是一个NSString对象,要想在水管里顺利流动,就要借RACSubject的力。
Commands
command 表现为RACCommand类,偷个懒直接举个例子吧,比如一个简单的注册界面:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
RACSignal *formValid=[RACSignal
combineLatest:@[
self.userNameField.rac_textSignal,
self.emailField.rac_textSignal,
]
reduce:^(NSString *userName,NSString *email){
return@(userName.length>0
&&email.length>0);
}];
RACCommand *createAccountCommand=[RACCommand commandWithCanExecuteSignal:formValid];
RACSignal *networkResults=[[[createAccountCommand
addSignalBlock:^RACSignal *(id value){
//... 网络交互代码
}]
switchToLatest]
deliverOn:[RACScheduler mainThreadScheduler]];
// 绑定创建按钮的 UI state 和点击事件
[[self.createButton rac_signalForControlEvents:UIControlEventTouchUpInside]executeCommand:createAccountCommand];
|
Sequences
sequence 表现为RACSequence类,可以简单看做是RAC世界的NSArray,RAC增加了-rac_sequence方法,可以使诸如NSArray这些集合类(collection classes)直接转换为RACSequence来使用。
Schedulers
scheduler 表现为RACScheduler类,类似于GCD,but schedulers support cancellationbut schedulers support cancellation, and always execute serially.
ReactiveCocoa的简单使用
实践出真知,下面就举一些简单的例子,一起看看RAC的使用
Subscription
接收 -subscribeNext: -subscribeError: -subscribeCompleted:
|
1
2
3
4
5
6
7
|
RACSignal *letters=[@"A B C D E F G H I"componentsSeparatedByString:@" "].rac_sequence.signal;
// 依次输出 A B C D…
[letters subscribeNext:^(NSString *x){
NSLog(@"%@",x);
}];
|
Injecting effects
注入效果 -doNext: -doError: -doCompleted:,看下面注释应该就明白了:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
__block unsignedsubscriptions=0;
RACSignal *loggingSignal=[RACSignal createSignal:^RACDisposable *(id<RACSubscriber>subscriber){
subscriptions++;
[subscriber sendCompleted];
returnnil;
}];
// 不会输出任何东西
loggingSignal=[loggingSignal doCompleted:^{
NSLog(@"about to complete subscription %u",subscriptions);
}];
// 输出:
// about to complete subscription 1
// subscription 1
[loggingSignal subscribeCompleted:^{
NSLog(@"subscription %u",subscriptions);
}];
|
Mapping
-map: 映射,可以看做对玻璃球的变换、重新组装
|
1
2
3
4
5
6
7
|
RACSequence *letters=[@"A B C D E F G H I"componentsSeparatedByString:@" "].rac_sequence;
// Contains: AA BB CC DD EE FF GG HH II
RACSequence *mapped=[letters map:^(NSString *value){
return[value stringByAppendingString:value];
}];
|
Filtering
-filter: 过滤,不符合要求的玻璃球不允许通过
|
1
2
3
4
5
6
7
|
RACSequence *numbers=[@"1 2 3 4 5 6 7 8 9"componentsSeparatedByString:@" "].rac_sequence;
// Contains: 2 4 6 8
RACSequence *filtered=[numbers filter:^BOOL(NSString *value){
return(value.intValue%2)==0;
}];
|
Concatenating
-concat: 把一个水管拼接到另一个水管之后
|
1
2
3
4
5
6
|
RACSequence *letters=[@"A B C D E F G H I"componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers=[@"1 2 3 4 5 6 7 8 9"componentsSeparatedByString:@" "].rac_sequence;
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *concatenated=[letters concat:numbers];
|
Flattening
-flatten:
Sequences are concatenated
|
1
2
3
4
5
6
7
|
RACSequence *letters=[@"A B C D E F G H I"componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers=[@"1 2 3 4 5 6 7 8 9"componentsSeparatedByString:@" "].rac_sequence;
RACSequence *sequenceOfSequences=@[letters,numbers].rac_sequence;
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *flattened=[sequenceOfSequences flatten];
|
Signals are merged (merge可以理解成把几个水管的龙头合并成一个,哪个水管中的玻璃球哪个先到先吐哪个玻璃球)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
RACSubject *letters=[RACSubject subject];
RACSubject *numbers=[RACSubject subject];
RACSignal *signalOfSignals=[RACSignal createSignal:^RACDisposable *(id<RACSubscriber>subscriber){
[subscriber sendNext:letters];
[subscriber sendNext:numbers];
[subscriber sendCompleted];
returnnil;
}];
RACSignal *flattened=[signalOfSignals flatten];
// Outputs: A 1 B C 2
[flattened subscribeNext:^(NSString *x){
NSLog(@"%@",x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];
|
Mapping and flattening
-flattenMap: 先 map 再 flatten
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
RACSequence *numbers=[@"1 2 3 4 5 6 7 8 9"componentsSeparatedByString:@" "].rac_sequence;
// Contains: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
RACSequence *extended=[numbers flattenMap:^(NSString *num){
return@[num,num].rac_sequence;
}];
// Contains: 1_ 3_ 5_ 7_ 9_
RACSequence *edited=[numbers flattenMap:^(NSString *num){
if(num.intValue%2==0){
return[RACSequence empty];
}else{
NSString *newNum=[num stringByAppendingString:@"_"];
return[RACSequence return:newNum];
}
}];
RACSignal *letters=[@"A B C D E F G H I"componentsSeparatedByString:@" "].rac_sequence.signal;
[[letters
flattenMap:^(NSString *letter){
return[database saveEntriesForLetter:letter];
}]
subscribeCompleted:^{
NSLog(@"All database entries saved successfully.");
}];
|
Sequencing
-then:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
RACSignal *letters=[@"A B C D E F G H I"componentsSeparatedByString:@" "].rac_sequence.signal;
// 新水龙头只包含: 1 2 3 4 5 6 7 8 9
//
// 但当有接收时,仍会执行旧水龙头doNext的内容,所以也会输出 A B C D E F G H I
RACSignal *sequenced=[[letters
doNext:^(NSString *letter){
NSLog(@"%@",letter);
}]
then:^{
return[@"1 2 3 4 5 6 7 8 9"componentsSeparatedByString:@" "].rac_sequence.signal;
}];
|
Merging
+merge: 前面在flatten中提到的水龙头的合并
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
RACSubject *letters=[RACSubject subject];
RACSubject *numbers=[RACSubject subject];
RACSignal *merged=[RACSignal merge:@[letters,numbers]];
// Outputs: A 1 B C 2
[merged subscribeNext:^(NSString *x){
NSLog(@"%@",x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];
|
Combining latest values
+combineLatest: 任何时刻取每个水龙头吐出的最新的那个玻璃球
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
RACSubject *letters=[RACSubject subject];
RACSubject *numbers=[RACSubject subject];
RACSignal *combined=[RACSignal
combineLatest:@[letters,numbers]
reduce:^(NSString *letter,NSString *number){
return[letter stringByAppendingString:number];
}];
// Outputs: B1 B2 C2 C3
[combined subscribeNext:^(idx){
NSLog(@"%@",x);
}];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
[numbers sendNext:@"2"];
[letters sendNext:@"C"];
[numbers sendNext:@"3"];
|
Switching
-switchToLatest: 取指定的那个水龙头的吐出的最新玻璃球
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
RACSubject *letters=[RACSubject subject];
RACSubject *numbers=[RACSubject subject];
RACSubject *signalOfSignals=[RACSubject subject];
RACSignal *switched=[signalOfSignals switchToLatest];
// Outputs: A B 1 D
[switched subscribeNext:^(NSString *x){
NSLog(@"%@",x);
}];
[signalOfSignals sendNext:letters];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[signalOfSignals sendNext:numbers];
[letters sendNext:@"C"];
[numbers sendNext:@"1"];
[signalOfSignals sendNext:letters];
[numbers sendNext:@"2"];
[letters sendNext:@"D"];
|
常用宏
RAC 可以看作某个属性的值与一些信号的联动
|
1
2
3
4
|
RAC(self.submitButton.enabled)=[RACSignal combineLatest:@[self.usernameField.rac_textSignal,self.passwordField.rac_textSignal]reduce:^id(NSString *userName,NSString *password){
return@(userName.length>=6&&password.length>=6);
}];
|
RACObserve 监听属性的改变,使用block的KVO
|
1
2
3
4
|
[RACObserve(self.textField,text)subscribeNext:^(NSString *newName){
NSLog(@"%@",newName);
}];
|
UI Event
RAC为系统UI提供了很多category,非常棒,比如UITextView、UITextField文本框的改动rac_textSignal,UIButton的的按下rac_command等等。
最后
有了RAC,可以不用去操心值什么时候到达什么时候改变,只需要简单的进行数据来了之后的步骤就可以了。
说了这么多,在回过头去看leezhong的比喻和该文最后总结的关系图,再好好梳理一下吧。我也是初学者,诚惶诚恐的呈上这篇博文,欢迎讨论,如有不正之处欢迎批评指正。
参考
https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/FrameworkOverview.mdhttps://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/BasicOperators.md
http://vimeo.com/65637501
http://iiiyu.com/2013/09/11/learning-ios-notes-twenty-eight/
http://blog.leezhong.com/ios/2013/06/19/frp-reactivecocoa.html
http://nshipster.com/reactivecocoa/
[转]使用ReactiveCocoa实现iOS平台响应式编程的更多相关文章
- 使用ReactiveCocoa实现iOS平台响应式编程
使用ReactiveCocoa实现iOS平台响应式编程 ReactiveCocoa和响应式编程 在说ReactiveCocoa之前,先要介绍一下FRP(Functional Reactive Prog ...
- ReactiveCocoa,最受欢迎的iOS函数响应式编程库(2.5版),没有之一!
简介 项目主页: ReactiveCocoa 实例下载: https://github.com/ios122/ios122 简评: 最受欢迎,最有价值的iOS响应式编程库,没有之一!iOS MVVM模 ...
- iOS响应式编程:ReactiveCocoa vs RxSwift 选谁好
转载: iOS响应式编程:ReactiveCocoa vs RxSwift 选谁好 内容来自stack overflow的一个回答:ReactiveCocoa vs RxSwift – pros an ...
- IOS响应式编程框架ReactiveCocoa(RAC)使用示例
ReactiveCocoa是响应式编程(FRP)在iOS中的一个实现框架,它的开源地址为:https://github.com/ReactiveCocoa/ReactiveCocoa# :在网上看了几 ...
- IOS响应式编程框架ReactiveCocoa(RAC)使用示例-备
ReactiveCocoa是响应式编程(FRP)在IOS中的一个实现框架,它的开源地址为:https://github.com/ReactiveCocoa/ReactiveCocoa# :在网上看了几 ...
- [iOS] 响应式编程开发-ReactiveCocoa(一)
什么是响应式编程 响应式编程是一种面向数据流和变化传播的编程范式.这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播. 例如,在命令式编程环境中 ...
- iOS开发之OC篇-响应式编程Reactive Cocoa
一.Reactive Cocoa 介绍 Reactive Cocoa 是 iOS 开发的一个 "重量级" 框架 高大上的概念:响应式编程 核心概念:信号 Signal 官方网站:h ...
- 函数响应式编程(FRP)框架--ReactiveCocoa
由于工作原因,有段时间没更新博客了,甚是抱歉,只是,从今天開始我又活跃起来了,哈哈,于是决定每周更新一博.大家互相学习.交流. 今天呢.讨论一下关于ReactiveCocoa,这个採用函数响应式编程( ...
- 深入浅出-iOS函数式编程的实现 && 响应式编程概念
简介 本篇主要回顾一下--iOS函数式编程 && 响应式编程概念 ,如何一步步实现函数式编程的过程,对阅读Masonry && SnapKit源码有一定的帮助. 配图 ...
随机推荐
- 从头开始学JavaScript (七)——函数
原文:从头开始学JavaScript (七)--函数 一.return 函数在执行完return之后停止并立即退出. return返回值:与return: 如下两个例子: function sum(n ...
- 【百度地图API】手机浏览器抓包工具及其使用方法
原文:[百度地图API]手机浏览器抓包工具及其使用方法 摘要:为了测试地图API在手机浏览器上的性能,需要给手机浏览器设置代理.通过代理,我们可以在PC上获取到抓包数据.进而对性能做进一步分析. -- ...
- 宏观CMS-->功能体系结构内容管理系统
CMS,Content Management System,一个非常普通的站点内容管理系统.本文章旨在从一定的高度把CMS的功能概念做一个分解论述 ,希望读者能够有所感. 1.前台 前台是站点中给 ...
- asp.net mvc3 数据验证(二)——错误信息的自定义及其本地化
原文:asp.net mvc3 数据验证(二)--错误信息的自定义及其本地化 一.自定义错误信息 在上一篇文章中所做的验证,在界面上提示的信息都是系统自带的,有些读起来比较生硬.比如: ...
- poj 3026 Borg Maze (bfs + 最小生成树)
链接:poj 3026 题意:y行x列的迷宫中,#代表阻隔墙(不可走).空格代表空位(可走).S代表搜索起点(可走),A代表目的地(可走),如今要从S出发,每次可上下左右移动一格到可走的地方.求到达全 ...
- 快速构建Windows 8风格应用2-创建调试应用
原文:快速构建Windows 8风格应用2-创建调试应用 本篇博文主要介绍的是创建应用时可以选择哪些模版,生成默认的Windows 8风格应用解决方案中含哪些文件,最后是如何调试Windows 8风格 ...
- erlang mnesia数据库简单应用
mnesia是erlang自带的分布式数据库,基于ets和dets实现的.mnesia兼顾了dets的持久性和ets的高性能,可以自动在多个erlang节点间同步数据库.最关键的是,mnesia实现了 ...
- Lightdm:奔跑吧GUI[已解决]
Fedora替换gdm为lightdm解决无法登陆问题 前两天安装Codeblocks,这货安装了很多包和依赖,直接导致我重启进步去界面,卡在fedora LOGO处,如下 实在忧伤,已经因为折腾不知 ...
- [译]在运行时内存中的Java对象是怎么样的
(文章翻译自What do Java objects look like in memory during run-time?) 我们知道函数在内存中是作为一个活动记录栈来实现的.而且我们知道Java ...
- 熔断器C#实现
关键词1:保险丝.电闸跳闸.输入密码错误3次则在指定的时间之内禁止登录 关键词2:保护性架构.防御性代码.软件可靠性 实现:https://github.com/fecktty/Circuit_Bre ...