ReactiveCocoa与Functional Reactive Programming

 

转自  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

FRP demo

这在重交互的应用里是很是有用的。以注册为例:react

register demo

提交按钮的状态要跟输入框的状态绑定,好比必选的输入框没有填完时,提交按钮是灰色的,也就是不可点;若是提交按钮不可点,那么文字变成灰色,否则变成蓝色;若是正在提交,那么输入框的文字颜色变成灰色,且不可点,否则变成默认色且可点;若是注册成功就在状态栏显示成功信息,否则显示错误信息,等等。ios

能够看到光是注册页就有这么多的联动,在javascript中能够采用事件监听来处理,iOS中更多的是delegate模式,本质上都是事件的分发和响应。这种作法的缺点是不够直观,尤为在逻辑比较复杂的状况下。这也是为何尽管nodejs很高效,但因为javascript的callback style和异步模式不符合正常的编程习惯,让不少人望而却步。git

使用FRP主要有两个好处:直观和灵活。直观的代码容易编写、阅读和维护,灵活的特性便于应对变态的需求。github

ReactiveCocoa

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在UITextFieldcategory中添加的,直接用便可。

combine signal

RAC的大统一

RAC统一了对KVO、UI Event、Network request、Async work的处理,由于它们本质上都是值的变化(Values over time)。

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;
}

小结

简单画了下关系图,罗列了些要点

关系图和要点

上面只是大概说了一下RAC的使用情景和用法,更多的例子能够到项目主页中查看。

参考

相关文章
相关标签/搜索