Functional Reactive Programming& ReactiveCocoa

FRP

ReactiveCocoa结合几种编程风格:

  1. 函数式编程(Functional Programming):使用高阶函数,例如函数用其他函数作为参数。
  2. 响应式编程(Reactive Programming):关于数据流和变化传播。
    被描述为函数响应式编程(FRP)框架。

什么是FunctionReactiveProgramming

a = 2;
b = 2;
c = a +b // c is 4

b = 3

如果使用FRP,c的值会随着ba的值而改变,所以叫做响应式编程。

FRP提供了一种信号机制来实现这样的效果,通过信号来记录值的变化。信号可以被叠加、分割或合并。通过对信号的组合,就不需要去监听某个值或事件。

Mou icon

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

RAC icon

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

总结图

posted @ 2015-08-11 19:34  pszertlek  阅读(132)  评论(0编辑  收藏  举报