最近看了一些关于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