本文英文原文出自这篇文字 http://www.raywenderlich.com/62699/reactivecocoa-tutorial-pt1 ,但我只是有选择性的进行了翻页
rac强调原子操作以及组装。rac基本上是建立在信号的基础上的,也就是RACSignal,所有的操作都能转成RACSignal来组装操作。
1、单个信号
rac入门最经典的一个例子就是一个登录界面,如下
只有当用户名和密码都满足的时候,高亮sign in 按钮。
传统的做法,你需要在delegate里面监听输入文字的变化,并做校验,这样至少同一个逻辑的代码是分散开的,而且还需要写很多额外代码,那么rac里面,会是怎样了。代码如下
[self.usernameTextField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
一行代码足矣。。。其中 self . usernameTextField . rac_textSignal就是一个RACSignal,RAC对许多基础组建都封装了RACSignal,并不需要我们自己去创建。
在username除连续输入3个d,输出如下
2016-02-19 20:37:42.309 ReactiveExample[71930:6364937] d
2016-02-19 20:37:42.582 ReactiveExample[71930:6364937] dd
2016-02-19 20:37:42.952 ReactiveExample[71930:6364937] ddd
是不是很简单!
如果你想对用户名进行校验,如需要长度大于3,那么
RACSignal *filteredUsername = [usernameSourceSignal
filter:^BOOL(id value) {
NSString *text = value;
return text.length > 3;
}];
[filteredUsername subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
输出如下
2016-02-19 20:50:42.069 ReactiveExample[72046:6445227] dddd
2016-02-19 20:50:43.530 ReactiveExample[72046:6445227] ddddd
2016-02-19 20:50:44.858 ReactiveExample[72046:6445227] dddddd
当输入的字符个数大于3的时候,就会触发输出。这里简单介绍两个概念 1)信号,也就是RACSignal对象 2)订阅,如上面的
[filteredUsername subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
这里,表示对filteredUsername进行了订阅,当filteredUsername发出信号的时候,就会被订阅者感知到。
其中filter是一个rac操作,它的作用是将满足条件的usernameSourceSignal信号转化成了filteredUsername信号,rac有非常多这种操作,有兴趣的可以查看起官网文档。当然上面的代码,你也可以组合在一起,如下
[self.usernameTextField.rac_textSignal
filter:^BOOL(NSString *text) {
return text.length > 3;
}]
subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
上面的filter只是一个过滤操作,其实产生的新信号filteredUsername本质上就是usernameSourceSignal,只不过是满足一定条件的usernameSourceSignal。在rac中,你完全可以将一个信号转化成一个完成不同的信号。见如下代码
[[[self.usernameTextField.rac_textSignal
map:^id(NSString *text) {
return @(text.length);
}]
filter:^BOOL(NSNumber *length) {
return [length integerValue] > 3;
}]
subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
跟上面只有filter进行同样的操作,如下
2016-02-19 21:03:14.344 ReactiveExample[72125:6500152] 4
2016-02-19 21:03:15.112 ReactiveExample[72125:6500152] 5
2016-02-19 21:03:15.806 ReactiveExample[72125:6500152] 6
注意对比会发现,这里输出的不再是输入的dddd ddddd dddddd,而是d的个数了,现在已经是一个完全不同的信号了。这是因为我们对
self.usernameTextField.rac_textSignal进行了map操作,形成新的信号,而这个信号传递的是@(text.length),事实上,这里我们可以传递任何对象
2、两个信号
上面只考虑了单个信号的情况,现在我们考虑两个信号的情况
RACSignal *validUsernameSignal =
[self.usernameTextField.rac_textSignal
map:^id(NSString *text) {
return @([self isValidUsername:text]);
}];
RACSignal *validPasswordSignal =
[self.passwordTextField.rac_textSignal
map:^id(NSString *text) {
return @([self isValidPassword:text]);
}];
现在我们做一个考虑,当用户名,或者密码正确的时候,输入框显示clearColor否则现在yellowcolor。比如对密码做这种校验
[[validPasswordSignal
map:^id(NSNumber *passwordValid) {
return [passwordValid boolValue] ? [UIColor clearColor] : [UIColor yellowColor];
}]
subscribeNext:^(UIColor *color) {
self.passwordTextField.backgroundColor = color;
}];
当将两者考虑在一起,可以如下
RAC(self.passwordTextField, backgroundColor) =
[validPasswordSignal
map:^id(NSNumber *passwordValid) {
return [passwordValid boolValue] ? [UIColor clearColor] : [UIColor yellowColor];
}];
RAC(self.usernameTextField, backgroundColor) =
[validUsernameSignal
map:^id(NSNumber *passwordValid) {
return [passwordValid boolValue] ? [UIColor clearColor] : [UIColor yellowColor];
}];
个是RAC框架提供的一种宏,用于将信号的输出直接赋值给绑定的对象。
接下来介绍怎么将这两个信号绑定到一起。回到最初的需求,我们需要在当用户名以及密码同时有效的情况下,高亮sign in按钮。这里需要用到combine操作。如下
RACSignal *signUpActiveSignal =
[RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]
reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid) {
return @([usernameValid boolValue] && [passwordValid boolValue]);
}];
combineLatest的作用是将最近的validUsernameSignal 以及 validPasswordSignal信号结合起来。reduce操作将这combineLatest起来的两个信号结合成一个信号,这个信号传递的值,可以根据这两个信号分别发出的信号结合起来,组成一个新的值。因此,sign in按钮的高亮可以根据如下方法来实现
[signUpActiveSignal subscribeNext:^(NSNumber *signupActive) {
self.signInButton.enabled = [signupActive boolValue];
}];
当sign in按钮高亮的时候,就可以开始处理sign in按钮的响应了,起响应代码如下
[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
NSLog(@"button clicked");
}];
3、自己创建信号
前面使用的信号都是RAC框架自带的,很多时候,我们也需要创建属于自己的信号,创建信号如下
-(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;
}];
}
这时,sign in 按钮的响应代码替换为
[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
map:^id(id x) {
return [self signInSignal];
}]
subscribeNext:^(id x) {
NSLog(@"Sign in result: %@", x);
}];
当sign in按钮被点击的时候,signInButton会发生一个信号,注意,在以前,我们只传递了对象,而这里传递了一个信号,会有什么不同呢,这里不会输出一个值,而是会输出一串地址,因为,这里属于信号的信号,而不是普通的信号,为了正常输出,我们需要使用flattenmap,修改如下
[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
flattenMap:^id(id x) {
return [self signInSignal];
}]
subscribeNext:^(NSNumber *signedIn) {
BOOL success = [signedIn boolValue];
self.signInFailureText.hidden = success;
if (success) {
[self performSegueWithIdentifier:@"signInSuccess" sender:self];
}
}];
flattenmap与map的区别在于,flattenmap会讲signal里面的值取出来,形成一个正常的signal,而map操作,如果碰到一个signal对象,它只是简单的将signal最为一个新的signal的值封装成一个信号的信号。
dd