体会 RAC 的魅力
大多数朋友可能和我一样,刚接触 RAC 的时候一头雾水~
so,我们通过一次体验来认识 RAC~
下面我要举个栗子:
我们对 username 和 password 进行监听,当输入任何值时候,我们打印it
PS:
- 栗子来源
- 栗子工程下载
咱们直接用起来,RAC 可以用如下代码解决:
subscribeNext–订阅者的回调 block
对usernameTextField的 text 进行监听,一旦有新值则调用 subscribeNext block
1 2 3
| [self.usernameTextField.rac_textSignal subscribeNext:^(id x){ NSLog(@"%@", x) }]
|
此时,结果如下:
filter–过滤
- 对usernameTextField的 text 进行监听
- 当filter 返回 YES 的时候,才调用 subscribeNext block
- 当filter 返回 NO 的时候,不调用 subscribeNext block
1 2 3 4 5 6 7 8
| [[self.usernameTextField.rac_textSignal filter:^BOOL(id value){ NSString *text = value; return text.length > 3; }] subscribeNext:^(id x){ NSLog(@"%@", x); }];
|
结果如下,从4个 q 开始输出
你会发现下面的代码和上面的代码有一样的效果,下面的代码中我将每一步分离出来了,更有效的帮助我们理解 RAC 的运行过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| RACSignal *usernameSourceSignal = self.usernameTextField.rac_textSignal; RACSignal *filteredUsername = [usernameSourceSignal filter:^BOOL(id value){ NSString*text = value; return text.length > 3; }]; [filteredUsername subscribeNext:^(id x){ NSLog(@"%@", x); }];
|
map–转换传值
map
从上一个next
事件接收数据,通过执行block
把自定义的返回值传给下一个next
事件。
1 2 3 4 5 6 7 8 9 10
| [[[self.usernameTextField.rac_textSignal map:^id(NSString*text){ return @(text.length); }] filter:^BOOL(NSNumber*length){ return[length integerValue] > 3; }] subscribeNext:^(id x){ NSLog(@"%@", x); }];
|
此时,结果如下,输出的变成了数字(字符串长度),而且从4开始输出:
在上面的代码中,map
以NSString
为输入,取字符串的长度,返回一个NSNumber
。
自定义 RAC
RAC
ReactiveCocoa signal(RACSignal)发送事件流给它的subscriber。目前总共有三种类型的事件:next
、error
、completed
。
一个signal在因error终止或者完成前,可以发送任意数量的next事件。
自定义RACSignal使用步骤
创建信号,didubscr
这个 block 会在有订阅者订阅信号的时候调用
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didubscr
订阅信号,才会激活信号,netBlock
这个block会在有信号发送数据时调用
- (RACDisposable *)subscribeNext:(void (^)(id x))netBlock
发送信号
- (void)sendNext:(idvalue
创建 RACSignal 对象
1 2 3 4 5 6 7 8 9 10 11
| 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]); }];
|
看看2个 Valid 方法
1 2 3 4 5 6 7 8 9
| - (BOOL)isValidUsername:(NSString *)username { return username.length > 3; } - (BOOL)isValidPassword:(NSString *)password { return password.length > 3; }
|
RACSignal创建好了,现在需要对RACSignal值改变的情况做不同的应对
1 2 3 4 5 6 7 8
| [[validPasswordSignal map:^id(NSNumber *passwordValid){ return[passwordValid boolValue] ? [UIColor clearColor]:[UIColor yellowColor]; }] subscribeNext:^(UIColor *color){ self.passwordTextField.backgroundColor = color; }];
|
执行结果如下:
PS: 不要使用上面
这段代码,下面
有一种更好的写法!上面这段代码只是帮助我们理解过程
1 2 3 4 5 6 7 8 9
| 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宏有两个参数,第一个是需要设置属性值的对象,第二个是属性名。每次信号产生一个next
事件,传递过来的值都会应用到该属性上。
combineLatest–聚合
现在的代码中已经有可以产生用户名和密码输入框是否有效的信号了——validUsernameSignal
和validPasswordSignal
了。现在需要做的就是聚合这两个信号来决定登录按钮是否可用。
1 2 3 4 5
| RACSignal *signUpActiveSignal = reduce:^id(NSNumber*usernameValid, NSNumber *passwordValid){ return @(&&); }];
|
上面的代码使用combineLatest:reduce:
方法把validUsernameSignal
和validPasswordSignal
产生的最新的值聚合在一起,并生成一个新的信号。每次这两个源信号的任何一个产生新值时,reduce block
都会执行,block
的返回值会发给下一个信号。
1 2 3
| [signUpActiveSignal subscribeNext:^(NSNumber*signupActive){ self.signInButton.enabled =[signupActive boolValue] }]
|
运行结果:
给button的 TouchUpInside 事件绑定一个信号,给这个信号添加了订阅,每当这个事件触发的时候,调用订阅的 block 方法
1 2 3
| [[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { NSLog(@"button clicked") }]
|
信号内信号- map 与 flattenMap
下面这个是给 button 的点击事件,添加一个信号,用 map 转换后,传给下一个订阅者打印出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| - (void)test { [[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id x){ return[self signInSignal] }] subscribeNext:^(id x){ NSLog(@"Sign in result: %@", x) }] } - (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 }] } - (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock { double delayInSeconds = 0.0 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)) dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ BOOL success = [username isEqualToString:@"user"] && [password isEqualToString:@"password"] completeBlock(success) }) }
|
这段代码我们想要执行的效果是监控到UIControlEventTouchUpInside
后判断输入的账号密码是否正确,并且将判断结果(BOOL)打印出来
实际结果:
这是什么鬼?说好的BOOL
呢?
分析一下,我们将 上面第一个方法分解后,如下
1 2 3 4 5 6 7 8 9 10
| RACSignal *singnal1 = [self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside]; RACSignal *singnal2 = [singnal1 map:^id(id x){ return[self signInSignal]; }]; [singnal2 subscribeNext:^(id x){ NSLog(@"Sign in result: %@", x); }];
|
可以看出每一步传递的都是一个 RACSingnal
信号,其中 map 调用了- (RACSignal *)signInSignal
是要把信号传递的参数改变了.
但是- (RACSignal *)signInSignal
内部自己又创建了一个信号,并且返回了这个信号,所以我们在 NSLog
打印的时候是一个乱七八糟鬼~代码如下
其实我们想要的是- (RACSignal *)signInSignal
内部[subscriber sendNext:@(success)];
这个传递的数据.
RAC封装了另外一种 flattenMap
方法来代替 map
处理这种信号中含有信号的情况,会自动转换
所以,遇到信号中包含另一个信号,并且想将sendNext:
发送的数据传到外部信号中,可以用flattenMap
代替map
1 2 3 4 5 6 7 8 9 10 11
| [[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:(id x){ return[self signInSignal] }] subscribeNext:^(NSNumber*signedIn){ BOOL success =[signedIn boolValue] self.signInFailureText.hidden = success; if(success){ [self performSegueWithIdentifier:@"signInSuccess" sender:self] } }]
|
subscribeNext:block
从登录信号中取得结果,相应地更新signInFailureText
是否可见。如果登录成功执行导航跳转。
doNext– 附加操作
当我们想在每一次按钮被点击,发送事件的过程中,做一点与事件本身并无联系的事儿,可以用 doNext:
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 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] } }]
|
上面的代码将每一次按钮点击后按钮本身置为不可点状态,并且隐藏错误文本,当校验失败时再重新恢复按钮为可点击状态,并且显示错误文本~
最终工程
上面栗子最终工程下载点我
RAC Tips
RAC 事件传递的值是什么?
可以看到下面的打印 X 为一个 button, NSLog
内的却是0
0来源于图1 block中传递的 BOOL, 那么 button 对象来源于哪儿?
从 button 订阅点击事件的信号中可以看到,调用 sendNext: 的对象是 subscriber
,但是:内传递的参数是 button 自己,所以外面打印的x 就是 button
so. 信号订阅者接收到得值是什么,取决于上一个信号 sendNext:
方法里发送的是什么
RAC使后感
- RAC 函数调用,环环相扣~e.g.调用形式:
[[[XXX map] filter] subscribeNext:Block]
- 每次调用方法的实例对象都是 RACSignal 对象
- RACSignal对象直接传递的值也是一个对象
- 代码中没有用来表示两个输入框有效状态的私有属性。这就是用
响应式编程
的一个关键区别,你不需要使用实例变量来追踪瞬时状态。
- 下一个订阅者收到的传递参数,取决于上一个RACSignal对象 sendNext 的值是什么
来源:
1.raywenderlich’s Blog
本站文章若无特别说明,均为原创文章,转载请注明地址: https://kevinmky.github.io