Context
反复接触 ReactiveCocoa
,这次真的准备把它应用到实际开发中了.为了以后使用方便,这里列出一些常用关键字的使用方法,以备查询.
常用方法
简单订阅 subscribeNext
使用场景:
“ 如果你改变了,让我知道 “
[self.usernameTextField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
过滤条件 filter
使用场景:
“ 如果你改变了,并且满足x条件, 那么再让我知道 “
[[self.usernameTextField.rac_textSignal
filter:^BOOL(id value) {
NSString* text = value;
return text.length > 4;
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
还有拆分的写法,因为 block层级太深 ,可读性不好:
RACSignal* usernameSourceSignal = self.usernameTextField.rac_textSignal;
RACSignal* filteredUsernameSignal =
[usernameSourceSignal filter:^BOOL(id value) {
NSString* text = value;
return text.length > 3;
}];
[filteredUsernameSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
类型转换 map
使用场景:
当需要从输入信号中提取不同的信息时(比如这里, 从打印下个字符,到打印长度)
[[[self.usernameTextField.rac_textSignal
map:^id(NSString* value) {
return @(value.length);
}] filter:^BOOL(NSNumber* value) {
return [value integerValue] > 4;
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
- 注意: 可 map 的只能是对象
RAC 宏
使用场景: RAC(A,b)
利用信号改变 A 的 b 属性值
// 验证信号
RACSignal *validUsernameSignal =
[self.usernameTextField.rac_textSignal
map:^id(NSString *text) {
return @([self isValidUsername:text]);
}];
RAC(self.usernameTextField, backgroundColor) =
[validUsernameSignal
map:^id(NSNumber *passwordValid){
return[passwordValid boolValue] ? [UIColor clearColor]:[UIColor yellowColor];
}];
聚合信号 combineLatest
使用场景:
多个信号条件同时满足, 才能产生有效信号(比如登陆的时候,用户名有效并且密码有效的时候,登陆按钮才应该有效)
RACSignal *signUpActiveSignal =
[
RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]
reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
return @([usernameValid boolValue]&&[passwordValid boolValue]);
}
];
- 使用combineLatest:reduce:方法把validUsernameSignal和validPasswordSignal产生的最新的值聚合在一起,并生成一个新的信号。每次这两个源信号的任何一个产生新值时,reduce block都会执行,block的返回值会发给下一个信号。
事件信号
使用场景:
拿到UIKit控件的事件响应信号
[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
NSLog(@"button clicked");
}];
封装方法
使用场景:
想把一个异步的API 封装成信号
- (RACSignal *)signInSignal {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber){
[self.signInService
signInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text
complete:^(BOOL success){
[subscriber sendNext:@(success)];
[subscriber sendCompleted];
}];
return nil;
}];
}
拿到信号中的信号 flattenMap
使用场景:
当需要从包含信号b的信号a中拿取信号b
[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
flattenMap:^id(id x){
return[self signInSignal];
}]
subscribeNext:^(id x){
NSLog(@"Sign in result: %@", x);
}];
添加附加操作(Adding side-effects)
使用场景:
需要进行一些准备工作的时候
[[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
doNext:^(id x){
self.signInButton.enabled =NO;
self.signInFailureText.hidden =YES;
}]
flattenMap:^id(id x){
return[self signInSignal];
}]
subscribeNext:^(NSNumber*signedIn){
self.signInButton.enabled =YES;
BOOL success =[signedIn boolValue];
self.signInFailureText.hidden = success;
if(success){
[self performSegueWithIdentifier:@"signInSuccess" sender:self];
}
}];
移除订阅 dispose
使用场景:
当需要手动释放一个信号(当没有订阅,信号就不复存在),但是使用场景很少,仅供了解
RACSignal *backgroundColorSignal =
[self.searchText.rac_textSignal
map:^id(NSString *text) {
return [self isValidSearchText:text] ?
[UIColor whiteColor] : [UIColor yellowColor];
}];
RACDisposable *subscription =
[backgroundColorSignal
subscribeNext:^(UIColor *color) {
self.searchText.backgroundColor = color;
}];
// at some point in the future ...
[subscription dispose];
防止循环引用
@weakify(self)
[[self.searchText.rac_textSignal
map:^id(NSString *text) {
return [self isValidSearchText:text] ?
[UIColor whiteColor] : [UIColor yellowColor];
}]
subscribeNext:^(UIColor *color) {
@strongify(self)
self.searchText.backgroundColor = color;
}];
next error completed
在signal的生命周期中,它可能不发送事件,发送一个或多个next事
件,在这之后还能发送一个completed事件或一个error事件。
[[self requestAccessToTwitterSignal]
subscribeNext:^(id x) {
NSLog(@"Access granted");
} error:^(NSError *error) {
NSLog(@"An error occurred: %@", error);
}];
信号链接 then
使用场景:
当后面的信号需要依赖前面的信号时
[[[self requestAccessToTwitterSignal]
then:^RACSignal *{
@strongify(self)
return self.searchText.rac_textSignal;
}]
subscribeNext:^(id x) {
NSLog(@"%@", x);
} error:^(NSError *error) {
NSLog(@"An error occurred: %@", error);
}];
then方法会等待completed事件的发送,然后再订阅由then block返回的signal。这样就高效地把控制权从一个signal传递给下一个。
then方法会跳过error事件,因此最终的subscribeNext:error: block还是会收到获取访问权限那一步发送的error事件。
异步信号
使用场景:
后台加载资源
-(RACSignal *)signalForLoadingImage:(NSString *)imageUrl {
RACScheduler *scheduler = [RACScheduler
schedulerWithPriority:RACSchedulerPriorityBackground];
return [[RACSignal createSignal:^RACDisposable *(id subscriber) {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
UIImage *image = [UIImage imageWithData:data];
[subscriber sendNext:image];
[subscriber sendCompleted];
return nil;
}] subscribeOn:scheduler];
}
- subscribeOn:来确保signal在指定的scheduler上执行。
在主线程上更新UI
cell.twitterAvatarView.image = nil;
[[[self signalForLoadingImage:tweet.profileImageUrl]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(UIImage *image) {
cell.twitterAvatarView.image = image;
}];
针对cell的重用问题, 有种更优化的方法:
[[[[self signalForLoadingImage:tweet.profileImageUrl]
takeUntil:cell.rac_prepareForReuseSignal]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(UIImage *image) {
cell.twitterAvatarView.image = image;
}];
延时响应 throttle
使用场景:
当用户输入完毕, 自定进行搜索的时候,不应该用户每次改变输入,都马上搜索,应该当用户停止输入 x 秒之后,再进行搜索
@weakify(self);
[[self.passwordTextField.rac_textSignal throttle:2] subscribeNext:^(id x) {
@strongify(self);
self.hintLabel.text = (NSString*)x;
}];
参考网址:
http://www.raywenderlich.com/62699/reactivecocoa-tutorial-pt1
http://benbeng.leanote.com/post/ReactiveCocoaTutorial-part1