(这篇文章原来发布在 csdn ,现在 blog 迁移过来,并用 Markdown 重新排版以及修改)
本文英文原文出自这篇文章 ,但我只是有选择性的进行了翻译。
rac 强调原子操作以及组装。rac 基本上是建立在信号的基础上的,也就是 RACSignal ,所有的操作都能转成 RACSignal 来组装操作。这篇文章主要从信号的角度进行介绍。
单个信号
rac入门最经典的一个例子就是一个登录界面,如下:
要求只有当用户名和密码都满足的时候,高亮 sign in 按钮。
要想实现这样的功能,传统的做法,你需要在 delegate 里面监听输入文字的变化,并做校验,这样至少同一个逻辑的代码是分散开的,而且还需要写很多额外代码,rac 里一个很大的特色就是能够将让代码不分,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的时候,才会触发输出。是不是很神奇!
不过在继续了解之前,我需要向大家简单介绍 RAC 中两个基本的概念
信号,也就是 RACSignal 对象。
订阅,如上面的 subscribeNext操作。
如下代码
[filteredUsername subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
filteredUsername是一个信号,subscribeNext 表示对信号 filteredUsername 进行了订阅。这样写的结果是,当 filteredUsername 发出信号的时候,就会被订阅者感知到。因此会输出log。
rac 基本方法
filter
在上面在做长度大于3的判断时,我们用到了 filter 操作。filter是一个 rac 操作,它的作用是将满足条件的usernameSourceSignal信号转化成了filteredUsername信号,rac 有非常多这种操作,有兴趣的可以查看起官网文档。当然上面的代码,你也可以组合在一起,如下
[self.usernameTextField.rac_textSignal
filter:^BOOL(NSString *text) {
return text.length > 3;
}]
subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
map
上面的 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),事实上,这里我们可以传递任何对象。
两个信号
上面只考虑了单个信号的情况,现在我们考虑两个信号的情况,见如下代码:
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() 是 RAC 框架提供的一种宏,用于将信号的输出直接赋值给绑定的对象。
这里还是单独处理的,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");
}];
自己创建信号
前面使用的信号都是 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);
}];
flattenmap
当 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 的值封装成一个信号的信号。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。