RAC常用操作

信号的操作可以分为两类:单个信号的变换、多个信号的组合

单个信号的操作

单个信号的变换也可以分为几类:值操作、数量操作、时间操作;

1 值操作:映射

  • Map : 将源信号内容映射成新对象;

    1. 传入一个block,类型是返回对象,参数是value,value就是源信号的内容,直接拿到源信号的内容做处理
    2. 把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值
    3. 当 signalA 事件流出现完成事件时, signalB 的事件流也会出现完成事件
    4. 当 signalA 事件流出现错误事件时, signalB 也会将该错误原封不动地释放出来
      RACSubject *subject = [RACSubject subject];
      RACSubject *bindSingal = [subject map:^id(id value) {
        return [NSString stringWithFormat:@"The King: %@",value];
      }];
      [bindSingal subscribeNext:^(id x) {
        NSLog(@"%@",x);
      }];
      [subject sendNext:@"Jon Snow"];
      
  • FlattenMap : 将源信号的内容映射成新的信号,并降堆;

    1. FlatternMap中的Block返回信号,而Map中返回对象;
    2. 若信号发出的是信号,则使用FlatternMap;
      RACSubject *singal = [RACSubject subject];
      RACSubject *singalOfSingals = [RACSubject subject];
      [[singalOfSingals flattenMap:^RACStream *(id value) {
         return value;
      }] subscribeNext:^(id x) {
         NSLog(@">>>>%@",x);
      }];
      [singalOfSingals sendNext:singal];
      [singal sendNext:@"123"];
      
  • map 操作还有一个简化版: mapReplace[signalA mapReplace:@886]; 等价于 [signalA map:^id(id value) { return @886; }]; 。

  • reduceEach : 是 map 的变体。当 signalA 的值事件包裹的数据是 RACTuple 类型时,才可以使用该操作;

  • 除了 reduceEach外, map还有其他的一些变体操作:

    - (RACSignal *)not;
    - (RACSignal *)and;
    - (RACSignal *)or;
    - (RACSignal *)reduceApply;
    

    reduceApply 也要求值事件包裹的数据类型是 RACTuple ,并且该 RACTuple 的第一个元素是一个block,后面的元素作为该block的参数传入,返回该block的执行结果。

2.1 数量操作:过滤

  • filter : 一般和文本框一起用,添加过滤条件;

    1. 当 signalA 产生完成事件时, signalB 也会产生完成事件
    2. 当 signalA 产生错误事件时, signalB 也会将该错误原封不动地释放出来
    [[self.textField.rac_textSignal filter:^BOOL(id value) {        
      return [value isEqualToString:@"王"];
    }] subscribeNext:^(id x) {
      self.contentLabel.text = x;
    }];
    
  • ignore : 忽略某些值,ignoreValues忽略所有值;

      RACSubject *subject = [RACSubject subject]; 
      [[subject ignore:@"Jon Snow"] subscribeNext:^(id x) {
          NSLog(@"ignore>>%@",x);
      }];
      [subject sendNext:@"Ned Stark"];
      [subject sendNext:@"Bobb Stark"];
      [subject sendNext:@"Jon Snow"]; //忽略
      [subject sendNext:@"Sansa Stark"];
    
  • skip:(NSUInteger)skipCount : 跳过前N个值;

      RACSubject *subject = [RACSubject subject];
      [[subject skip:2] subscribeNext:^(id x) {
          NSLog(@"%@",x);
      }];
      [subject sendNext:@"Ned Stark"];//过滤
      [subject sendNext:@"Bobb Stark"];//过滤
      [subject sendNext:@"Jon Snow"];
    
  • distinctUntilChanged : 如果当前值和上一次的值一样,就过滤掉;

      RACSubject *subject = [RACSubject subject];
      [[subject distinctUntilChanged] subscribeNext:^(id x) {
          NSLog(@"订阅:>>%@",x);
      }];
      [subject sendNext:@"Jon"];
      [subject sendNext:@"Bobb"];
      [subject sendNext:@"Jon"];
      [subject sendNext:@"King"];
      [subject sendNext:@"King"];//过滤
    
  • take:(NSUInteger)count : 只取前N个值 ; takeLast:(NSUInteger)count : 只取后N个值

      RACSubject *subject = [RACSubject subject];
      [[subject take:2] subscribeNext:^(id x) {
          NSLog(@"take>>%@",x);
      }];
      [[subject takeLast:2] subscribeNext:^(id x) {
          NSLog(@"takeLast>>%@",x);
      }];
      [subject sendNext:@"Ned Stark"];
      [subject sendNext:@"Bobb Stark"];
      [subject sendNext:@"Jon Snow"];
      [subject sendNext:@"Sansa Stark"];
      [subject sendCompleted]; //必须要调用sendComplete
    

2.2 其他数量操作

  • startWith : 操作的作用是在事件流的开始新增一个值事件。

  • repeat : 操作会忽略 signalA 的完成事件,但它不会忽略错误事件,换句话说,如果 signalA 的事件流中含有错误事件,那么 signalB 的事件流会和 signalA 完全一致。

  • retry : 当 signalA 事件流中含有完成事件时,那么 signalA 的事件流会和 signalA 完全一致,retry 操作还有带参数版本 retry: ,该版本可以指定次数。这种操作在处理网络任务时非常有用

  • Collect : 聚合数量。

  • AggregateScan : 这两个操作有些类似,但明显有区别,前者改变了事件流的事件数量,后者没有。

3 时间操作

  • 经常会有这样的需求:定时产生一个事件。

    + (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;
    + (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway;
    

    这两个类方法都会产生一个时间信号,时间信号会以一定频率产生一个值事件,值事件包裹的是 NSDate 对象。第二个方法中的参数 leeway 是「缓冲」、「余地」的意思,还不太明白其作用

  • Delay

  • Throttle 在下图中, signalA 事件流中一共有5个值事件和1个完成事件,其中1号和2号事件间隔2s,2、3、4号事件间隔1s,5号值事件比4号值事件晚3s,完成事件比5号事件晚1s。

    • signalB 由 [signalA throttle:1.5] 得到,throttle表示门限,其作用效果是:
    • 当 signalA 事件流产生一个值事件时,若1.5s内没有其他的值事件产生,则 signalB 事件流中也会产生该值事件;比如上图中的1号值事件,下一个值事件(2号)在其2s后产生,满足要求,故而在1.5s后, signalB 的事件流也产生该信号;
    • 当 signalA 事件流产生一个值事件时,若1.5s内有其他的值事件产生,则 signalB 会过滤掉该值事件;比如上图中的2号和3号值事件,在1s后分别有3号和4号信号产生,不满足要求,故而都不会出现在 signalB 的事件流中; 对于 signalA 中的最后一个值事件, signalB 事件流中总会也包含它。 这种信号有什么用呢?有一个常用的应用场景:在App内经常需要搜索,为了确保实时性,简单的做法是每输入一个字符就检索一下,但是若用户输入字符比较快,这种检索策略会比较浪费流量,因此比较好的做法是,用户输入某个字符后,1s(参考值)内没有再输入别的字符,就检索一次搜索结果。此时throttle就有了用武之地。

多个信号的组合

以 signalA + signalB => signalC 为例,需要留意几个问题: 组合得到 signalC 的信号受哪个信号终止而终止, signalA or signalB ?当 signalA 或 signalB 事件流中出现错误信号,会如何?各个信号何时开始被订阅?

  • concat : 使用需求:有两部分数据:想让上部分先执行,完了之后再让下部分执行(都可获取值); 当 signalA 的事件流中出现完成事件时,立马订阅 signalB , signalC 事件流的完成事件与 signalB 的完成事件对应。同时,注意到当 signalA 中出现错误事件时, signalC 中的事件流与 signalA 的事件流完全保持一致,就没 signalB 什么事儿了。

       RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
          NSLog(@"创建信号源A...");
          [subscriber sendNext:@"King"];
          [subscriber sendCompleted];
          return [RACDisposable disposableWithBlock:^{
              NSLog(@"信号源A销毁...");
          }];
      }];
    
      RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
          NSLog(@"创建信号源B...");
          [subscriber sendNext:@"Queen"];
          [subscriber sendCompleted];
          return [RACDisposable disposableWithBlock:^{
              NSLog(@"信号源B销毁...");
          }];
      }];
    
      RACSignal *concatSignal = [signalA concat:signalB];
      [concatSignal subscribeNext:^(id x) {
          NSLog(@"%@",x);
      }];
    

  • combineLatest : 把多个信号聚合成你想要的信号,使用场景:当多个输入框都有值的时候按钮才可点击。 思路:就是把输入框输入值的信号都聚合成按钮是否能点击的信号。

    RACSignal *combineSignal = [RACSignal combineLatest:@[_nameField.rac_textSignal,_pwdField.rac_textSignal,_textField.rac_textSignal]
         reduce:^id(NSString *name,NSString *pwd,NSString *content){
          NSLog(@"%@,%@,%@",name,pwd,content);
          return @(name.length && pwd.length && content.length);
      }];
    RAC(_observerBtn,enabled) = combineSignal;
    

    除了 signalC = [signalA combineLatest:signalB] 这种用法外,还可以这样:

      signalC = [RACSignal combineLatest:@[signalA, signalB]];
      signalC = [RACSignal combineLatest:RACTuplePack(signalA, signalB)];
    
  • zipWith : 把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元祖,才会触发压缩流的next事件。

      RACSubject *subjectA = [RACSubject subject];
      RACSubject *subjectB = [RACSubject subject];
      RACSignal *zipSignal = [subjectA zipWith:subjectB];
      [zipSignal subscribeNext:^(id x) {
          NSLog(@"%@",x); ////所有的值都被包装成了元组 RACTuple
      }];
      [subjectA sendNext:@"King"];
      [subjectB sendNext:@"Queen"];
    

    除了 signalC = [signalA zip:signalB] 这种用法外,还可以这样:

      signalC = [RACSignal zip:@[signalA, signalB]];
      signalC = [RACSignal zip:RACTuplePack(signalA, signalB)];
    
  • merge : 多个信号合并成一个信号,任何一个信号有新值就会调用,任何一个信号请求完成都会被订阅到; 除了 signalC = [signalA merge:signalB] 这种用法外,还可以这样:

      signalC = [RACSignal merge:@[signalA, signalB]];
      signalC = [RACSignal merge:RACTuplePack(signalA, signalB)];
    
     //merge基本使用
      RACSubject *subjectA = [RACSubject subject];
      RACSubject *subjectB = [RACSubject subject];
      RACSubject *subjectC = [RACSubject subject];
    
      RACSignal *mergeSignal = [subjectA merge:subjectB];
      RACSignal *finalSignal = [mergeSignal merge:subjectC];
    
      [finalSignal subscribeNext:^(id x) {
          NSLog(@"%@",x);
      }];
      [subjectA sendNext:@"King"];
      [subjectB sendNext:@"Queen"];
      [subjectC sendNext:@"Kin"];
    
  • Sample : 在 signalC = [signalA sample:signalB] 中, signalB 充当 signalA 的采样信号,一旦 signalA 或 signalB 先产生完成事件或者错误事件, signalC 的事件流就被终止。

  • takeUntil:(RACSignal *) : 给takeUntil传的是哪个信号,那么当这个信号发送信号或sendCompleted,就不能再接受源信号的内容了;

      RACSubject *subject = [RACSubject subject];
      RACSubject *subject2 = [RACSubject subject];
      [[subject takeUntil:subject2] subscribeNext:^(id x) {
          NSLog(@"%@",x);
      }];
      [subject sendNext:@"Ned Stark"];
      [subject sendNext:@"Bobb Stark"];
      [subject2 sendNext:@"Jon Snow"];
    //    [subject2 sendCompleted];
      [subject sendNext:@"Sansa Stark"]; //不再接受
    
  • takeUntilReplacement:(RACSignal *) : signalC 事件流中的事件和 signalA 一一对应,一旦 signalB 事件流中出现了信号, signalC 的事件就和 signalB 形成呼应,当然前提是 signalC 的事件流还没有终结。

  • then : 使用需求:有两部分数据:想让上部分先进行网络请求但是过滤掉数据,然后进行下部分的,拿到下部分数据。

     RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
          NSLog(@"创建信号源A...");
          [subscriber sendNext:@"King"];
          [subscriber sendCompleted];
          return [RACDisposable disposableWithBlock:^{
              NSLog(@"信号源A销毁...");
          }];
      }];
    
      RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { 
          NSLog(@"创建信号源B...");
          [subscriber sendNext:@"Queen"];
          [subscriber sendCompleted];
          return [RACDisposable disposableWithBlock:^{
              NSLog(@"信号源B销毁...");
          }];
      }];
    
      RACSignal *thenSignal = [signalA then:^RACSignal *{
          return signalB;
      }];
      [thenSignal subscribeNext:^(id x) {
          NSLog(@"%@",x);
      }];
    

switchToLatest

    // 创建信号中信号  
    RACSubject *signalofsignals = [RACSubject subject];  
    RACSubject *signalA = [RACSubject subject];  
     // 订阅信号  
    [signalofsignals subscribeNext:^(RACSignal *x) {  
        [x subscribeNext:^(id x) {  
            NSLog(@"%@", x);  
        }];  
    }];  

    // switchToLatest: 获取信号中信号发送的最新信号  
    [signalofsignals.switchToLatest subscribeNext:^(id x) {  
        NSLog(@"%@", x);  
    }];  
    // 发送信号  
    [signalofsignals sendNext:signalA];  
    [signalA sendNext:@4];

RAC开发中常见用法。

代替代理:

  • rac_signalForSelector:用于替代代理。
    // rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
    [[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
       NSLog(@"点击红色按钮");
    }];
    

代替KVO :

  • rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。
    // 把监听redV的center属性改变转换成信号,只要值改变就会发送信号
    // observer:可以传入nil
    [[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] 
    subscribeNext:^(id x) {
       NSLog(@"%@",x);
    }];
    

监听事件:

  • rac_signalForControlEvents:用于监听某个事件(非自定义控件)。
    // 把按钮点击事件转换为信号,点击按钮,就会发送信号
    [[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
       NSLog(@"按钮被点击了");
    }];
    

代替通知:

  • rac_addObserverForName:用于监听某个通知。
    // 把监听到的通知转换信号
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification 
       object:nil] subscribeNext:^(id x) {
       NSLog(@"键盘弹出");
    }];
    

监听文本框文字改变:

  • rac_textSignal:只要文本框发出改变就会发出这个信号。
    [_textField.rac_textSignal subscribeNext:^(id x) {
      NSLog(@"文字改变了%@",x);
    }];
    

处理当界面有多次请求时,需要都获取到数据时,才能展示界面

  • rac_liftSelector:withSignalsFromArray:Signals:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法。
  • 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据
    // 处理多个请求,都返回结果的时候,统一做处理.
    RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
       [subscriber sendNext:@"发送请求1"];
       return nil;
    }];
    RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
       [subscriber sendNext:@"发送请求2"];
       return nil;
    }];
    // 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
    [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
    }
    // 更新UI
    - (void)updateUIWithR1:(id)data r2:(id)data1
    {
    NSLog(@"更新UI%@  %@",data,data1);
    }
    

ReactiveCocoa常见宏

  • RAC(TARGET, [KEYPATH, [NIL_VALUE]]):用于给某个对象的某个属性绑定。

    // 只要文本框文字改变,就会修改label的文字
    RAC(self.labelView,text) = _textField.rac_textSignal;
    
  • RACObserve(self, name):监听某个对象的某个属性,返回的是信号。

    [RACObserve(self.view, center) subscribeNext:^(id x) {
          NSLog(@"%@",x);
    }];
    
  • @weakify(Obj)@strongify(Obj),一般两个都是配套使用,在主头文件ReactiveCocoa.h中并没有导入,需要自己手动导入,RACEXTScope.h才可以使用。但是每次导入都非常麻烦,只需要在主头文件自己导入就好了(解决循环引用问题,弱化/强化指针)。

  • RACTuplePack/RACTupleUnpack:把数据包装成RACTuple(元组类)/把RACTuple(元组类)解包成对应的数据。

    // 把参数中的数据包装成元组
     RACTuple *tuple = RACTuplePack(@"xmg",@20);
    
     // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
     // name = @"xmg" age = @20
    RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
    

results matching ""

    No results matching ""