Functional Reactive Programming& ReactiveCocoa
FRP
ReactiveCocoa结合几种编程风格:
- 函数式编程(Functional Programming):使用高阶函数,例如函数用其他函数作为参数。
- 响应式编程(Reactive Programming):关于数据流和变化传播。
被描述为函数响应式编程(FRP)框架。
什么是FunctionReactiveProgramming
a = 2;
b = 2;
c = a +b // c is 4
b = 3
如果使用FRP,c
的值会随着b
和a
的值而改变,所以叫做响应式编程。
FRP提供了一种信号机制来实现这样的效果,通过信号来记录值的变化。信号可以被叠加、分割或合并。通过对信号的组合,就不需要去监听某个值或事件。
ReactiveCocoa
ReactiveCocoa是github上的一个开源项目,是在iOS平台上对FRP的实现。RRP的核心是信号,信号在ReactiveCoca中是用RACSignal来表示的,信号是数据流,可以被绑定和传递。
可以把信号想象成水龙头,只不过里面不是水,而是玻璃球,直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是现行处理,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。可以在水龙头上加一个过滤嘴(filter),不符合的不让通过,也可以加一个改动装置,把球改变成符合自己的需求(map)。也可以把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。
下面通过一个简单的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的大统一
RAC统一了对KVO、UI Event、Network request、Aysnc work 的处理,因为他们本质上都是值得变化。
KVO
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);
}];
UI Event
RAC还为系统UI提供了很多category,来方便消息的创建和传递,比如按钮被点击或文本框有改动等等,上面的例子中self.firstNameField.rac_textSignal,在对应的文本框有改动时,会自动向数据流中添加新的数据,绑定该消息的其他消息就会收到新的数据,如果有subscriber的话,会自动触发。
Network Request && Async work
这些可以通过自定义信号,也就是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;
}