使用ReactiveCocoa实现iOS平台响应式编程
使用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]]; // 绑定创建button的 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
unsigned subscriptions = 0;
RACSignal *loggingSignal
= [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
subscriptions++;
[subscriber
sendCompleted];
return
nil;
}];
// 不会输出不论什么东西
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];
return nil;
}];
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:^(id x) {
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
https://github.com/ReactiveCocoa/ReactiveCocoa/blob/master/Documentation/FrameworkOverview.md
https://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.htmlhttp://nshipster.com/reactivecocoa/
使用ReactiveCocoa实现iOS平台响应式编程的更多相关文章
- [转]使用ReactiveCocoa实现iOS平台响应式编程
原文:http://www.itiger.me/?p=38 使用ReactiveCocoa实现iOS平台响应式编程 ReactiveCocoa和响应式编程 在说ReactiveCocoa之前,先要介绍 ...
- 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源码有一定的帮助. 配图 ...
随机推荐
- mysql 创建函数
<pre name="code" class="html">root 用户创建函数: delimiter $$ CREATE FUNCTION `l ...
- C++晋升之std中vector的实现原理(标准模板动态库中矢量的实现原理)
我们实现的数据结构是为了解决在执行过程中动态的开辟空间使用(比如我们不停的输入,输入的多少我们不确定) 假设当你看到这篇文章的话,就当作是零食咀嚼,营养没有有BUG,能够直接看我博客中文章:CPU对内 ...
- 14.5.7 Storing InnoDB Undo Logs in Separate Tablespaces 存储InnoDB Undo logs 到单独的表空间
14.5.7 Storing InnoDB Undo Logs in Separate Tablespaces 存储InnoDB Undo logs 到单独的表空间 在MySQL 5.6.3,你可以存 ...
- SDL Guide 中文译版
SDL即Simple DirectMedia Layer,类似DirectX,是完整的游戏.多媒体开发包,但不同的是它跨越几乎所有的平台,有各种语言的接口,多种语言的文档,而这一切都是广大志愿者完成的 ...
- 第二章 IoC Setter注入
Setter注入又称为属性注入.是通过属性的setXXX()方法来注入Bean的属性值或依赖对象.由于Setter注入具有可选择性和灵活性高的优点,因此Setter注入是实际应用中最常用的注入方式. ...
- android在Canvas使用drawBitmap画一幅画
1.画图的主要方法 //Bitmap:图片对象,left:向左偏移.top: 顶部偏移 drawBitmap(Bitmap bitmap, float left, float top, Pai ...
- 关于Oralce数据库优化的几点总结
个人理解,数据库性能最关键的因素在于IO,因为操作内存是快速的,但是读写磁盘是速度很慢的,优化数据库最关键的问题在于减少磁盘的IO,就个人理解应该分为物理的和逻辑的优化, 物理的是指oracle产品本 ...
- Program1-1
package com.algo; import java.util.Random; import java.util.Scanner; public class P1_1 { static int ...
- STM32M CUBE实现printf打印调试信息以及实现单字节接收
在写单片机程序时我们一般喜欢使用printf来通过串口打印调试信息,但这个函数是不能够直接使用的.必须做点对库函数的修改. 具体project下载地址: http://download.csdn.ne ...
- GIT的下载、安装、与使用
一.下载: 网址:https://code.google.com/p/msysgit/ 进入这个网站以后,你会看到以下界面: 在这个界面中找到: 这时你便可以下载GIT 二.安装 安装过程比较简单,一 ...