ReactiveCocoa初体验

Reactive的学习和使用

Podfile文件内容:

1
2
3
platform :ios, '7.0'

pod 'ReactiveCocoa', '2.1.8'

运行$ pod install

安装完成后使用xcworkspace文件打开项目

RWViewController.m文件

1
#import <ReactiveCocoa/ReactiveCocoa.h>

在viewDidLoad中添加代码

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

现在我们就能体验到信号编程了

可以看到我们每次输出一个字符都会产生一个信号,这大概就是信号编程吧
我们看看信号传递流程

现在我们将添加的代码修改一下

1
2
3
4
5
6
[[self.usernameTextField.rac_textSignal filter:^BOOL(id value) {
NSString *text = value;
return text.length > 3;
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];

我们可以看到,当满足filter的条件之后,才会产生信号,后面的条件才会继续执行,有一种链式操作的感觉
我们将上面的代码的完整结构

1
2
3
4
5
6
7
8
RACSignal *usernameSoucesSignal = self.usernameTextField.rac_textSignal;
RACSignal *filteredUsername = [usernameSoucesSignal filter:^BOOL(id value) {
NSString *text = value;
return text.length > 3;
}];
[filteredUsername subscribeNext:^(id x) {
NSLog(@"%@", x);
}];

可以看出来,每次文本框内容的改变就会产生一个信号对象,调用信号处理的block,得到结果
很显然,我们更喜欢上面那种简洁的语法

  • 现在让我们更进一步,看看到底发生了写什么:
1
2
3
4
5
6
7
8
9
 [[[self.usernameTextField.rac_textSignal map:^id(id value) {
NSString *text = value;
return @(text.length);
}] filter:^BOOL(id value) {
NSNumber *length = value;
return [length integerValue] > 3;
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];

信号传递流程

我比较笨,假设你跟我一样,并没有发现有什么问题,现在我们再改改代码:

1
2
3
4
5
6
7
8
9
 [[[self.usernameTextField.rac_textSignal map:^id(id value) {
NSString *text = value;
return text;
}] filter:^BOOL(id value) {
NSString *text = value;
return text.length > 3;
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];

这下能看懂了吧,map解析字符串然后向下传递,filter通过返回YES来过滤字符串,subscribeNext得到过滤后的字符串,如果没有满足过滤条件,信号将不会向下传递,能理解信号传递的思路了么

  • 创建验证状态信号
1
2
3
4
5
6
7
8
9
RACSignal *validUsernameSignal = [self.usernameTextField.rac_textSignal map:^id(id value) {
NSString *text = value;
return @([self isValidUsername:text]);
}];

RACSignal *validPasswordSignal = [self.passwordTextField.rac_textSignal map:^id(id value) {
NSString *text = value;
return @([self isValidPassword:text]);
}];

得到验证信号后,解析然后设置文本框背景与用户交互
不过我们不采用下面的写法,使用更好的写法

1
2
3
4
5
6
7
[[validPasswordSignal map:^id(id value) {
NSNumber *passwordValid = value;
return [passwordValid boolValue] ? [UIColor clearColor] :[UIColor yellowColor];
}] subscribeNext:^(id x) {
UIColor *color = x;
self.passwordTextField.backgroundColor = color;
}];

我们使用宏定义的方式将解析后返回的信号进行绑定处理

1
2
3
4
5
6
7
8
9
RAC(self.passwordTextField, backgroundColor) = [validPasswordSignal map:^id(id value) {
NSNumber *passwordValid = value;
return [passwordValid boolValue] ? [UIColor clearColor] : [UIColor yellowColor];
}];

RAC(self.usernameTextField, backgroundColor) = [validUsernameSignal map:^id(id value) {
NSNumber *usernameValid = value;
return [usernameValid boolValue] ? [UIColor clearColor] : [UIColor yellowColor];
}];

可以看到代码,我们将文本框和背景颜色绑定为一个信号处理,当验证信号解析后将自动设置
我们来看看通常我们一般这样写

1
2
self.usernameTextField.backgroundColor = self.usernameIsValid ? [UIColor clearColor] : [UIColor yellowColor];
self.passwordTextField.backgroundColor = self.passwordIsValid ? [UIColor clearColor] : [UIColor yellowColor];

这个能看懂吧,平常我们是这样写的,只是改变了编程方式,有一种链式操作的感觉,能将信号绑定处理,其实还不错
信号传递流程是这样的

  • 组合信号

在viewDidLoad添加代码:

1
2
3
4
5
6
7
RACSignal *signUpActiveSignal = [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
return @([usernameValid boolValue] && [passwordValid boolValue]);
}];

[signUpActiveSignal subscribeNext:^(NSNumber *signupActive) {
self.signInButton.enabled = [signupActive boolValue];
}];

将两个信号组合生成一个新的信号进行解析处理

我们这样修改后,就可以移除下面的这些无用的代码了

1
2
3
4
5
6
7
8
9
10
@property (nonatomic) BOOL passwordIsValid;
@property (nonatomic) BOOL usernameIsValid;

// handle text changes for both text fields
[self.usernameTextField addTarget:self
action:@selector(usernameTextFieldChanged)
forControlEvents:UIControlEventEditingChanged];
[self.passwordTextField addTarget:self
action:@selector(passwordTextFieldChanged)
forControlEvents:UIControlEventEditingChanged];

同时可以移除updateUIState,usernameTextFieldChangedpasswdTextFieldChanged方法等并删除多余的代码
现在的信号传递流程是这样的

现在代码是不是更简洁,更方便

  • 下一步,将其余的业务逻辑也使用信号处理

打开Main.storyboard,删除绑定的sign up按钮事件
继续在viewDidLoad中添加事件

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

可以看到下面的效果,事件也可以绑定为信号,感觉好6

现在我们可以移除signInButtonTouched:事件,并创建信号
增加一个方法

1
2
3
4
5
6
7
8
9
10
- (RACSignal *)signInSingal
{
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;
}];
}

在viewDidLoad中添加代码

1
2
3
4
5
[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id value) {
return [self signInSingal];
}] subscribeNext:^(id x) {
NSLog(@"Singal in result %@", x);
}];

信号传递流程:

下面来修改一下代码:

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

当点击按钮时验证通过得到1,验证失败得到0
现在我们让验证通过得到1时跳转

1
2
3
4
5
6
7
8
9
10
11
[[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id value) {
return [self signInSingal];
}] flattenMap:^RACStream *(id value) {
return [self signInSingal];
}] subscribeNext:^(NSNumber *signedIn) {
BOOL success = [signedIn boolValue];
self.signInFailureText.hidden = success;
if (success) {
[self performSegueWithIdentifier:@"signInSuccess" sender:self];
}
}];

现在,我们就使用ReactiveCocoa替代了原来的业务处理,比较简洁,但是也比较难理解
效果图:

修改代码,在信号传递中加一个过程

1
2
3
4
5
6
7
8
9
10
11
12
[[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
self.signInButton.enabled = NO;
self.signInFailureText.hidden = YES;
}] flattenMap:^RACStream *(id value) {
return [self signInSingal];
}] subscribeNext:^(NSNumber *signedIn) {
self.signInButton.enabled = YES;
BOOL success = [signedIn boolValue];
if(success) {
[self performSegueWithIdentifier:@"signInSuccess" sender:self];
}
}];

再运行看看,细节更加完善

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×