作为一个iOS开发者,你写的每一行代码几乎都是在响应某个事件,例如按钮的点击,收到网络消息,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。但是这些事件都用不同的方式来处理,比如action、delegate、KVO、callback等。ReactiveCocoa为事件定义了一个标准接口,从而可以使用一些基本工具来更容易的连接、过滤和组合。
如果你对上面说的还比较疑惑,那还是继续往下看吧。
ReactiveCocoa结合了几种编程风格:
函数式编程(Functional Programming):使用高阶函数,例如函数用其他函数作为参数。
响应式编程(Reactive Programming):关注于数据流和变化传播。
所以,你可能听说过ReactiveCocoa被描述为函数响应式编程(FRP)框架。
这就是这篇教程要讲的内容。编程范式是个不错的主题,但是本篇教程的其余部分将会通过一个例子来实践。
Reactive Playground
通过这篇教程,一个简单的范例应用Reactive Playground ,你将会了解到响应式编程。下载初始工程,然后编译运行一下确保你已经把一切都设置正确了。
Reactive Playground是一个非常简单的应用,它为用户展示了一个登录页。在用户名框输入user,在密码框输入password,然后你就能看到有一只可爱小猫咪的欢迎页了。
呀,真是可爱啊。
现在可以花一些时间来看一下初始工程的代码。很简单,用不了多少时间。
打开RWViewController.m看一下。你多快能找到控制登录按钮是否可用的条件?判断显示/隐藏登录失败label的条件是什么?在这个相对简单的例子里,可能只用一两分钟就能回答这些问题。但是对于更复杂的例子,这些所花的时间可能就比较多了。
使用ReactiveCocoa,可以使应用的基本逻辑变得相当简洁。是时候开始啦。
添加ReactiveCocoa框架
添加ReactiveCocoa框架最简单的方法就是用CocoaPods。如果你从没用过CocoaPods,那还是先去看看CocoaPods简介这篇教程吧。请至少看完教程中初始化的步骤,这样你才能安装框架。
注意:如果不想用CocoaPods,你仍然可以使用ReactiveCocoa,具体查看Github文档中引入ReactiveCocoa的步骤描述。
译注:我就是不喜欢用CocoaPods的那波人。所以我首先使用了Github上提供的方法,但是在第二步执行bootstrap时提示缺少xctool,我就果断放弃了,还是乖乖用CocoaPods吧。
具体怎么使用CocoaPods安装就不详细讲解了。
开动
就像在介绍中提到的,RAC为应用中发生的不同事件流提供了一个标准接口。在ReactiveCocoa术语中这个叫做信号(signal),由RACSignal类表示。
打开应用的初始view controller,RWViewController.m ,引入ReactiveCocoa的头文件。
1 |
#import <ReactiveCocoa/ReactiveCocoa.h> |
不要替换已有的代码,将下面的代码添加到viewDidLoad方法的最后:
1 2 3 |
[self.usernameTextField.rac_textSignal subscribeNext:^(id x){ NSLog(@"%@", x); }]; |
编译运行,在用户名输入框中输几个字。注意console的输出应该和下面的类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
2013-12-24 14:48:50.359 RWReactivePlayground[9193:a0b] i 2013-12-24 14:48:50.436 RWReactivePlayground[9193:a0b] is 2013-12-24 14:48:50.541 RWReactivePlayground[9193:a0b] is 2013-12-24 14:48:50.695 RWReactivePlayground[9193:a0b] is t 2013-12-24 14:48:50.831 RWReactivePlayground[9193:a0b] is th 2013-12-24 14:48:50.878 RWReactivePlayground[9193:a0b] is thi 2013-12-24 14:48:50.901 RWReactivePlayground[9193:a0b] is this 2013-12-24 14:48:51.009 RWReactivePlayground[9193:a0b] is this 2013-12-24 14:48:51.142 RWReactivePlayground[9193:a0b] is this m 2013-12-24 14:48:51.236 RWReactivePlayground[9193:a0b] is this ma 2013-12-24 14:48:51.335 RWReactivePlayground[9193:a0b] is this mag 2013-12-24 14:48:51.439 RWReactivePlayground[9193:a0b] is this magi 2013-12-24 14:48:51.535 RWReactivePlayground[9193:a0b] is this magic 2013-12-24 14:48:51.774 RWReactivePlayground[9193:a0b] is this magic? |
可以看到每次改变文本框中的文字,block中的代码都会执行。没有target-action,没有delegate,只有signal和block。令人激动不是吗?
ReactiveCocoa signal(RACSignal)发送事件流给它的subscriber。目前总共有三种类型的事件:next、error、completed。一个signal在因error终止或者完成前,可以发送任意数量的next事件。在本教程的第一部分,我们将会关注next事件。在第二部分,将会学习error和completed事件。
RACSignal有很多方法可以来订阅不同的事件类型。每个方法都需要至少一个block,当事件发生时就会执行block中的逻辑。在上面的例子中可以看到每次next事件发生时,subscribeNext:方法提供的block都会执行。
ReactiveCocoa框架使用category来为很多基本UIKit控件添加signal。这样你就能给控件添加订阅了,text field的rac_textSignal就是这么来的。
原理就说这么多,是时候开始让ReactiveCocoa干活了。
ReactiveCocoa有很多操作来控制事件流。假设你只关心超过3个字符长度的用户名,那么你可以使用filter操作来实现这个目的。把之前加在viewDidLoad中的代码更新成下面的:
1 2 3 4 5 6 7 8 |
[[self.usernameTextField.rac_textSignal filter:^BOOL(id value){ NSString*text = value; return text.length > 3; }] subscribeNext:^(id x){ NSLog(@"%@", x); }]; |
编译运行,在text field只能怪输入几个字,你会发现只有当输入超过3个字符时才会有log。