当前位置: 首页 > 工具软件 > ReactiveCocoa > 使用案例 >

ReactiveCocoa的学习笔记

唐哲
2023-12-01

最近看了一些关于ReactiveCocoa的东西,现将其纪录下来。

ReactiveCocoa是由Github工程师们开发的一个应用于iOS和OS X开发的函数响应式编程新框架。
在iOS开发中,按钮的点击,收到网络消息,属性的变化(通过KVO)等都是不同的事件,这些事件都用不同的方式来处理,如代理方法、block 回调、target-action 机制、通知、KVO 等,而ReactiveCocoa为事件定义了一个标准接口,可以通过一个统一通用的消息传递分发机制来处理各种不同的事件。

信号源(RACSignal)
在 ReactiveCocoa 中,信号源代表的是随着时间而改变的值流。RACSignal 可以向订阅者发送三种不同类型的事件:
next :RACSignal 通过 next 事件向订阅者传送新的值,并且这个值可以为 nil ;
error :RACSignal 通过 error 事件向订阅者表明信号在正常结束前发生了错误;
completed :RACSignal 通过 completed 事件向订阅者表明信号已经正常结束,不会再有后续的值传送给订阅者。
ReactiveCocoa 中的值流只包含正常的值,即通过 next 事件传送的值,并不包括 error 和 completed 事件,它们需要被特殊处理。通常情况下,一个信号的生命周期是由任意个 next 事件和一个 error 事件或一个 completed 事件组成的。
对于 RACSignal 类簇来说,最核心的方法莫过于 -subscribe: 了,这个方法封装了订阅者对信号源的一次订阅过程,它是订阅者与信号源产生联系的唯一入口。因此,对于 RACSignal 的所有子类来说,这个方法的实现逻辑就代表了该子类的具体订阅行为,是区分不同子类的关键所在。同时,这也是为什么 RACSignal 中的 -subscribe: 方法是一个抽象方法,并且必须要让子类实现的原因:

- (RACDisposable *)subscribe:(id)subscriber {
  NSCAssert(NO, @"This method must be overridden by subclasses");
  return nil;
}

订阅者(RACSubscriber)
Subscriber 负责承接signal 传递的数据。为了获取信号源中的值,需要对信号源进行订阅。在 ReactiveCocoa 中,订阅者是一个抽象的概念,所有实现了 RACSubscriber 协议的类都可以作为信号源的订阅者。一个signal没有Subscriber时什么也不干,此时处于冷状态。当有了Subscriber时才可以传递消息,处于热状态。
如下代码,只是创建了一个信号,并没有订阅者,所以处在冷信号阶段。

RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { 
    NSLog(@"triggered"); 
    [subscriber sendNext:@"signal"]; 
    [subscriber sendCompleted]; 
    return nil; 
}]; 

订阅信号,使信号处于热状态,如下代码:

[[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        NSLog(@"triggered");
        [subscriber sendNext:@"signal"];
        [subscriber sendCompleted];
        return nil;
    }] subscribeCompleted:^{
        NSLog(@"subscription");
    }];

ReactiveCocoa框架中常见Category
RAC在UIkit中加的Category,如:

UITextField+RACSignalSupport.h
UIButton+RACCommandSupport.h
UIAlertView+RACSignalSupport.h
...

常用的UIKit框架中的类也都有添加相应的Category。比如UIAlertView,利用RAC提供的分类就不需要再用Delegate了。

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"" message:@"Alert" delegate:nil cancelButtonTitle:@"YES" otherButtonTitles:@"NO", nil];
    [[alertView rac_buttonClickedSignal] subscribeNext:^(NSNumber *indexNumber) {
        if ([indexNumber intValue] == 1) {
            NSLog(@"you selected NO");
        } else {
            NSLog(@"you selected YES");
        }
    }]; 
    [alertView show];

其他常见类的Category

NSArray+RACSequenceAdditions.h
NSDictionary+RACSequenceAdditions.h
NSNotificationCenter+RACSupport.h
...

数据转换
Signal是很灵活的,它可以被修改(map), 过滤(filter), 叠加(combine), 串联(chain),这有助于应对更加复杂环境。
使用map操作通过block改变了事件中的数据。使用filter对事件中的数据进行过滤。

[[[self.testTextField.rac_textSignal
  map:^id(NSString* text){
    return @(text.length);
  }]
  filter:^BOOL(NSNumber*length){
    return[length integerValue] > 5;
  }]
  subscribeNext:^(id x){
    NSLog(@"%@", x);
  }];

map以NSString为输入,取字符串的长度,返回一个NSNumber。对传入的NSNumber类型数据,使用filter进行过滤,在输入数据后,当字符个数大于5时,打印出来的是所有大于5的数字。
对信号进行聚合(combine)

RAC(self.logInButton, enabled) = [RACSignal 
        combineLatest:@[ 
            self.usernameTextField.rac_textSignal, 
            self.passwordTextField.rac_textSignal 
        ] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) { 
            return @(username.length > 0 && password.length > 0 ); 
        }]; 

RAC(self.logInButton, enabled)运用到了RAC中定义的宏,RAC宏允许直接把信号的输出应用到对象的属性上。RAC宏有两个参数,第一个是需要设置属性值的对象,第二个是属性名。每次信号产生一个next事件,传递过来的值都会应用到该属性上。使用combineLatest:reduce:方法把上面的两个信号聚合在一起,并生成一个新的信号。每次这两个源信号的任何一个产生新值时,reduce block都会执行,block的返回值会发给下一个信号。RACsignal的这个方法可以聚合任意数量的信号,reduce 中block的参数和每个源信号相关。

参考的资料:
http://www.cocoachina.com/ios/20150123/10994.html
http://www.cocoachina.com/cms/wap.php?action=article&id=14880
http://www.cocoachina.com/industry/20140115/7702.html
http://benbeng.leanote.com/post/ReactiveCocoaTutorial-part2

 类似资料: