rac初识

(这篇文章原来发布在 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 中两个基本的概念

  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] 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 的值封装成一个信号的信号。

相关文章
相关标签/搜索