转自 http://blog.leezhong.com/ios/2013/06/19/frp-reactivecocoa.htmljavascript
Functional Reactive Programming(如下简称FRP)是一种响应变化的编程范式。先来看一小段代码html
a = 2
b = 2
c = a + b // c is 4
b = 3
// now what is the value of c?
若是使用FRP,c
的值将会随着b
的值改变而改变,因此叫作「响应式编程」。比较直观的例子就是Excel,当改变某一个单元格的内容时,该单元格相关的计算结果也会随之改变。java
FRP提供了一种信号机制来实现这样的效果,经过信号来记录值的变化。信号能够被叠加、分割或合并。经过对信号的组合,就不须要去监听某个值或事件。node
这在重交互的应用里是很是有用的。以注册为例:react
提交按钮的状态要跟输入框的状态绑定,好比必选的输入框没有填完时,提交按钮是灰色的,也就是不可点;若是提交按钮不可点,那么文字变成灰色,否则变成蓝色;若是正在提交,那么输入框的文字颜色变成灰色,且不可点,否则变成默认色且可点;若是注册成功就在状态栏显示成功信息,否则显示错误信息,等等。ios
能够看到光是注册页就有这么多的联动,在javascript中能够采用事件监听来处理,iOS中更多的是delegate模式,本质上都是事件的分发和响应。这种作法的缺点是不够直观,尤为在逻辑比较复杂的状况下。这也是为何尽管nodejs很高效,但因为javascript的callback style和异步模式不符合正常的编程习惯,让不少人望而却步。git
使用FRP主要有两个好处:直观和灵活。直观的代码容易编写、阅读和维护,灵活的特性便于应对变态的需求。github
ReactiveCocoa是github去年开源的一个项目,是在iOS平台上对FRP的实现。FRP的核心是信号,信号在ReactiveCocoa(如下简称RAC)中是经过RACSignal
来表示的,信号是数据流,能够被绑定和传递。web
能够把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径同样,这样就能保证玻璃球是依次排列,不会出现并排的状况(数据都是线性处理的,不会出现并发状况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。能够在水龙头上加一个过滤嘴(filter),不符合的不让经过,也能够加一个改动装置,把球改变成符合本身的需求(map)。也能够把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会获得这个球。objective-c
下面经过一个简单的demo来演示这个模型。
假如对象的某个属性想绑定某个消息,可使用RAC
这个宏,至关于给玻璃球套了一个水龙头。
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);
}];
这样,若是用户名和密码框的长度都超过6,提交按钮就enable了。反之,若是没符合要求,就会处于非开启状态。
能够看到usernameField
有了一个新的属性rac_textSignal
,这是RAC在UITextField
category中添加的,直接用便可。
RAC统一了对KVO、UI Event、Network request、Async work的处理,由于它们本质上都是值的变化(Values over time)。
RAC能够用来监测属性的改变,这点跟KVO很像,不过使用了block,而不是-observeValueForKeyPath:ofObject:change:context:
[RACAble(self.username) subscribeNext:^(NSString *newName) {
NSLog(@"%@", newName);
}];
使用起来是否是比KVO舒服多了。比KVO更增强大的是信号能够被链起来(chain)
// 只有当名字以'j'开头,才会被记录
[[RACAble(self.username)
filter:^(NSString *newName) {
return [newName hasPrefix:@"j"];
}]
subscribeNext:^(NSString *newName) {
NSLog(@"%@", newName);
}];
RAC还为系统UI提供了不少category,来方便消息的建立和传递,好比按钮被点击或文本框有改动等等,上面的例子中self.firstNameField.rac_textSignal
,在对应的文本框有改动时,会自动向数据流中添加新的数据,绑定该消息的其余消息就会收到新的数据,若是有subscriber的话,会自动触发。
这些能够经过自定义信号,也就是RACSubject
(继承自RACSignal
,能够理解为自由度更高的signal)来搞定。好比一个异步网络操做,能够返回一个subject,而后将这个subject绑定到一个subscriber或另外一个信号。
- (void)doTest
{
RACSubject *subject = [self doRequest];
[subject subscribeNext:^(NSString *value){
NSLog(@"value:%@", value);
}];
}
- (RACSubject *)doRequest
{
RACSubject *subject = [RACSubject subject];
// 模拟2秒后获得请求内容
// 只触发1次
// 尽管subscribeNext什么也没作,但若是没有的话map是不会执行的
// subscribeNext就是定义了一个接收体
[[[[RACSignal interval:2] take:1] map:^id(id _){
// the value is from url request
NSString *value = @"content fetched from web";
[subject sendNext:value];
return nil;
}] subscribeNext:^(id _){}];
return subject;
}
简单画了下关系图,罗列了些要点
上面只是大概说了一下RAC的使用情景和用法,更多的例子能够到项目主页中查看。