ReactiveCocoa 使用速查

Context

反复接触 ReactiveCocoa ,这次真的准备把它应用到实际开发中了.为了以后使用方便,这里列出一些常用关键字的使用方法,以备查询.

常用方法

简单订阅 subscribeNext

使用场景:
“ 如果你改变了,让我知道 “

1
2
3
[self.usernameTextField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];

过滤条件 filter

使用场景:

“ 如果你改变了,并且满足x条件, 那么再让我知道 “

1
2
3
4
5
6
7
8
[[self.usernameTextField.rac_textSignal

filter:^BOOL(id value) {
NSString* text = value;
return text.length > 4;
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];

还有拆分的写法,因为 block层级太深 ,可读性不好:

1
2
3
4
5
6
7
8
9
10
11
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

使用场景:

当需要从输入信号中提取不同的信息时(比如这里, 从打印下个字符,到打印长度)

1
2
3
4
5
6
7
8
[[[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 属性值

1
2
3
4
5
6
7
8
9
10
11
12
// 验证信号
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

使用场景:
多个信号条件同时满足, 才能产生有效信号(比如登陆的时候,用户名有效并且密码有效的时候,登陆按钮才应该有效)

1
2
3
4
5
6
7
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控件的事件响应信号

1
2
3
4
5
[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
NSLog(@"button clicked");
}];

封装方法

使用场景:

想把一个异步的API 封装成信号

1
2
3
4
5
6
7
8
9
10
11
12
- (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

1
2
3
4
5
6
7
8
[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
flattenMap:^id(id x){
return[self signInSignal];
}]
subscribeNext:^(id x){
NSLog(@"Sign in result: %@", x);
}];

添加附加操作(Adding side-effects)

使用场景:
需要进行一些准备工作的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[[[[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

使用场景:

当需要手动释放一个信号(当没有订阅,信号就不复存在),但是使用场景很少,仅供了解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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];​

防止循环引用

1
2
3
4
5
6
7
8
9
10
@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事件。

1
2
3
4
5
6
[[self requestAccessToTwitterSignal] 
subscribeNext:^(id x) {
NSLog(@"Access granted");
} error:^(NSError *error) {
NSLog(@"An error occurred: %@", error);
}];​

信号链接 then

使用场景:
当后面的信号需要依赖前面的信号时

1
2
3
4
5
6
7
8
9
10
[[[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事件。

异步信号

使用场景:
后台加载资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-(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

1
2
3
4
5
6
7
cell.twitterAvatarView.image = nil; 

[[[self signalForLoadingImage:tweet.profileImageUrl]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(UIImage *image) {
cell.twitterAvatarView.image = image;
}];

针对cell的重用问题, 有种更优化的方法:

1
2
3
4
5
6
[[[[self signalForLoadingImage:tweet.profileImageUrl] 
takeUntil:cell.rac_prepareForReuseSignal]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(UIImage *image) {
cell.twitterAvatarView.image = image;
}];

延时响应 throttle

使用场景:

当用户输入完毕, 自定进行搜索的时候,不应该用户每次改变输入,都马上搜索,应该当用户停止输入 x 秒之后,再进行搜索

1
2
3
4
5
@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