(这篇文章原来发布在 csdn ,如今 blog 迁移过来,并用 Markdown 从新排版以及修改)react
本文英文原文出自这篇文章 ,但我只是有选择性的进行了翻译。框架
rac 强调原子操做以及组装。rac 基本上是创建在信号的基础上的,也就是 RACSignal ,全部的操做都能转成 RACSignal 来组装操做。这篇文章主要从信号的角度进行介绍。ide
rac入门最经典的一个例子就是一个登陆界面,以下:
要求只有当用户名和密码都知足的时候,高亮 sign in 按钮。spa
要想实现这样的功能,传统的作法,你须要在 delegate 里面监听输入文字的变化,并作校验,这样至少同一个逻辑的代码是分散开的,并且还须要写不少额外代码,rac 里一个很大的特点就是可以将让代码不分,rac 实现上面的功能将会很简介,代码以下:翻译
[self.usernameTextField.rac_textSignal subscribeNext:^(id x) { NSLog(@"%@", x); }];
一行代码足矣。。。其中 self.usernameTextField.rac_textSignal 就是一个RACSignal , RAC 对许多基础组建都封装了 RACSignal ,并不须要咱们本身去建立。运行上面代码,而后在 username 输入框中连续输入 3 个 d,输出以下code
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
是否是很简单!orm
不只如此,若是你还想对用户名进行校验,好比要求用户名的长度大于3,那么,你只须要将上面的代码改成以下便可:对象
RACSignal *filteredUsername = [usernameSourceSignal filter:^BOOL(id value) { NSString *text = value; return text.length > 3; }]; [filteredUsername subscribeNext:^(id x) { NSLog(@"%@", x); }];
输出以下blog
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。
在上面在作长度大于3的判断时,咱们用到了 filter 操做。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),事实上,这里咱们能够传递任何对象。
上面只考虑了单个信号的状况,如今咱们考虑两个信号的状况,见以下代码:
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); }];
当 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 的值封装成一个信号的信号。