rac初识

(这篇文章原来发布在 csdn ,现在 blog 迁移过来,并用 Markdown 重新排版以及修改)本文英文原文出自这篇文章 ,但我只是有选择性的进行了翻译。rac 强调原子操作以及组装。rac 基本上是建立在信号的基础上的,也就是 RACSignal ,所有的操作都能转成 RACSignal 来组装操作。这篇文章主要从信号的角度进行介绍。单个信号rac入门最经典的一个

(这篇文章原来发布在 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] d2016-02-19 20:37:42.582 ReactiveExample[71930:6364937] dd2016-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] dddd2016-02-19 20:50:43.530 ReactiveExample[72046:6445227] ddddd2016-02-19 20:50:44.858 ReactiveExample[72046:6445227] dddddd

当输入的字符个数大于3的时候,才会触发输出。是不是很神奇!

不过在继续了解之前,我需要向大家简单介绍 RAC 中两个基本的概念

  1. 信号,也就是 RACSignal 对象。

  2. 订阅,如上面的 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] 42016-02-19 21:03:15.112 ReactiveExample[72125:6500152] 52016-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 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 的值封装成一个信号的信号。

关键字:xcode