美文网首页iOS Developer
ReactiveCocoa入门篇二

ReactiveCocoa入门篇二

作者: 小球why | 来源:发表于2016-12-12 14:20 被阅读120次

前言

通过之前入门篇一的学习,我们知道了怎么使用RAC以及基本的用法,这篇继续深入学习一下~


RAC的核心概念是信号,通过RACSignal来表示,信号是数据流,可以被绑定和传递。

冷信号和热信号

冷热信号的概念源于.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable,两者的区别是:

1. Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。
2. Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。

RAC框架各组件

  • RACStream类

RACSignal的父类,本身意义不大,一般会以RACSignal或者RACSequence等这些更高层次的表现形态代替

  • RACSignal类

RAC的核心就是signal,RACSignal有三种事件,通过-subscribeNext:error:completed:可以对Next、Error、Completed三种事件作出相应反应,RACSignal可以发送任意多个Next事件,和一个Error或者Completed事件

  • RACSubject类

可以认为是“可变的(Mutable)”信号/自定义信号,它嫁接非RAC代码到Signal世界的桥梁

  • RACCommand类
    可以认为是回应某些动作的信号,通常触发该信号都是UI控件

  • RACSequence类

可以简单看做RAC世界的NSArray,RAC增加了-rac_sequence方法,可以使诸如NSArray这些集合类直接转换为RACSequence来使用。

  • RACSeheduler类

RACScheduler类,类似GCD,但RACScheduler类支持撤销,并且总是运行安群的。RACScheduler是RAC里面对新城的简单封装,事件可以在指定的RACSchedler上分发和执行,不特殊指定的话,事件的分发和执行都在一个默认的后台线程里面做,大多数情况也就不用动了。有一些特殊的Signal必须在主线程调用,使用-deliverOn可以切换调用的线程

创建信号

简单创建一个信号如下代码:

//创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"next"];
    [subscriber sendCompleted];
    return nil;
}];

//订阅信号
[signal subscribeNext:^(id x) {
    NSLog(@"%@",x);
} error:^(NSError *error) {
    NSLog(@"%@",error);
} completed:^{
    NSLog(@"completed");
}];

输出如下:

2016-12-12 09:54:53.250 RACDemo[1183:33523] next
2016-12-12 09:54:53.250 RACDemo[1183:33523] completed

这样我们就创建了一个信号,通过subscriber我们可以发送不同的next,error或者completed信心,然后在subscribeNext:可以分别订阅到这些信息

常用用法

filter

filter可以起到过滤的作用,可以用来过滤掉一些无效信息

[[self.textField.rac_textSignal
  filter:^BOOL(NSString*text){
    return text.length > 3;
  }]
  subscribeNext:^(id x){
    NSLog(@"%@", x);
  }];

输出如下:

2016-12-12 09:55:22.507 RACDemo[1183:33523] 1111
2016-12-12 09:55:23.142 RACDemo[1183:33523] 11111
2016-12-12 09:55:23.883 RACDemo[1183:33523] 111111

可以看到,只要textField里面的内容长度大于3的才会输出

map

map操作可以用来把接受的数据转换成想要的类型

[[[self.textField.rac_textSignal
   map:^id(NSString*text){
       return @(text.length);
   }]
  filter:^BOOL(NSNumber*length){
      return[length integerValue] > 3;
  }]
 subscribeNext:^(id x){
     NSLog(@"%@", x);
 }];

输出如下:

2016-12-12 10:12:01.591 RACDemo[1299:42629] 4
2016-12-12 10:12:03.551 RACDemo[1299:42629] 5
2016-12-12 10:12:04.399 RACDemo[1299:42629] 6

可以看到,map操作通过block改变了事件的数据。map从上一个next事件接收数据,通过执行block把返回值传给下一个next事件。

使用map操作来把接受的数据转换成想要的类型,只要是对象就行,如果map返回的是基本数据类型(如NSInteger),为了将它作为事件的内容,可以用@()封装成对象

既然说到了map,那么接下来顺便说说flattenMap

flattenMap

先看一下一段代码

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"next"];
    return nil;
}];
    
[[[self.button
   rac_signalForControlEvents:UIControlEventTouchUpInside]
  map:^id(id x){
      return signal;
  }]
 subscribeNext:^(id x){
     NSLog(@"signal = %@", x);
 }];

输出如下:

2016-12-12 10:22:34.721 RACDemo[1385:48349] signal = <RACDynamicSignal: 0x6080000337e0> name: 

发现输出的是一个RACDynamicSignal对象,不是我们想要的next

解决这个问题只要把上面的map改成flattenMap就可以了,输出如下:

2016-12-12 10:27:03.109 RACDemo[1414:50435] signal = next

这才是我想要的东西,为什么会这样呢?

这是因为map创建一个新的信号,信号的value是block(value),也就是说,如果block(value)是一个信号,那么就是信号的value仍然是信号。如果是flattenMap则会继续调用这个信号的value,作为新的信号的value

RAC宏

RAC宏允许直接把信号的输出应用到对象的属性上。RAC有两个参数,第一个是设置属性值的对象,第二个是属性名。每次信号产生一个next事件,传递过来的值都会应用到该属性上。

RACSignal *textSignal = [self.textField.rac_textSignal
    map:^id _Nullable(NSString * _Nullable value) {
        return value;
}];
RAC(self.textField, backgroundColor) =
[textSignal
 map:^id(NSString *str){
     return [str length]>3 ? [UIColor blueColor]:[UIColor yellowColor];
 }];

可以看到,当textField里输入内容长度大于3的时候,textField的背景就会变成蓝色,否则就是黄色

combineLatest

    //定义2个自定义信号
    RACSubject *letters = [RACSubject subject];
    RACSubject *numbers = [RACSubject subject];
    //组合信号
    [[RACSignal combineLatest:@[letters, numbers]
                       reduce:^(NSString *letter, NSString *number){
                           //把2个信号的信号值进行字符串拼接
                           return [letter stringByAppendingString:number];
                       }] subscribeNext:^(NSString * x) {
                           NSLog(@"%@", x);
                       }];
    //自己控制发送信号值
    [letters sendNext:@"A"];
    [letters sendNext:@"B"];
    [numbers sendNext:@"1"];//打印B1
    [letters sendNext:@"C"];//打印C1
    [numbers sendNext:@"2"];//打印C2

RACSignal的combineLatest:reduce:方法可以聚合任意数量的信号,reduce block的参数和每个源信号有关,当其中任何一个源信号产生新值时,reduce block都会执行,block的返回值会发给下一个信号

  • 聚合——多个信号可以聚合成一个新的信号,实际上可以聚合并产生任何类型的信号

throttle

throttle可以起到节流作用

在我们做搜索框的时候,有时候需求的时实时搜索,即用户每每输入字符,view都要求展现搜索结果。这时如果用户搜索的字符串较长,那么由于网络请求的延时可能造成UI显示错误,并且多次不必要的请求还会加大服务器的压力,这显然是不合理的,此时我们就需要用到节流。

[[[self.textField rac_textSignal] throttle:0.5 ] subscribeNext:^(NSString * _Nullable x) {
    NSLog(@"%@",x);
}];
}];

takeUntil

takeUntil方法:在给定signal完成前一直取值

//创建取值信号
RACSignal *takeSignal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    //创建一个定时器信号,每隔1秒触发一次
    RACSignal *signal = [RACSignal interval:1
                                onScheduler:[RACScheduler mainThreadScheduler]];
    //定时接收
    [signal subscribeNext:^(id x) {
    [subscriber sendNext:@"等等~"];
    }];
    return nil;
}];
//创建条件信号
RACSignal *conditionSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                                 (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"时间到了");
        [subscriber sendCompleted];
    });
    return nil;
}];
    
//设置条件,塔克Signal信号是在conditionSignal信号接收完成前,不断地取值
[[takeSignal takeUntil:conditionSignal] subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@",x);
}];

输出如下:

2016-12-12 11:49:50.209 RACDemo[1938:91165] 等等~
2016-12-12 11:49:51.209 RACDemo[1938:91165] 等等~
2016-12-12 11:49:52.209 RACDemo[1938:91165] 等等~
2016-12-12 11:49:53.209 RACDemo[1938:91165] 等等~
2016-12-12 11:49:54.208 RACDemo[1938:91165] 等等~
2016-12-12 11:49:54.208 RACDemo[1938:91165] 时间到了

ignore

忽略信号,执行一个任意类型的数据,在发送时进行判断,如果相同该信号就会被忽略发送

[[[self.textField rac_textSignal] ignore:@"111"] subscribeNext:^(NSString * _Nullable x) {
    NSLog(@"%@",x);
}];

输出如下:

2016-12-12 12:08:57.251 RACDemo[2028:99269] 1
2016-12-12 12:08:57.812 RACDemo[2028:99269] 11
2016-12-12 12:08:58.779 RACDemo[2028:99269] 1111

这样111就会被忽略了

zipWith

zipWith能起到合并压缩的作用

//创建信号A
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"天真活泼"];
    return nil;
}];
    
//创建信号B
RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"机智可爱"];
    return nil;
}];
    
//合并后出来的是压缩包,需要解压才能取得到里面的值
[[signalA zipWith:signalB] subscribeNext:^(id  _Nullable x) {
    //解压缩
    RACTupleUnpack(NSString *stringA,NSString *stringB) = x;
    NSLog(@"我是一个%@%@的孩子",stringA,stringB);
}];

输出:

2016-12-12 12:18:14.808 RACDemo[2089:103473] 我是一个天真活泼机智可爱的孩子

then

then可以起到维持秩序的作用,then方法会等待completed事件的发送,然后在订阅有then block返回的signal。这样就高效地把控制权从一个signal传递给下一个

[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    NSLog(@"第一步");
    [subscriber sendCompleted];
    return nil;
}] then:^RACSignal *{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"第二步");
        [subscriber sendCompleted];
        return nil;
    }];
}] then:^RACSignal *{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSLog(@"第三步");
        [subscriber sendCompleted];
        return nil;
    }];
}] subscribeCompleted:^{
    NSLog(@"完成");
}];
    

输出如下:

2016-12-12 12:23:15.482 RACDemo[2148:107451] 第一步
2016-12-12 12:23:15.482 RACDemo[2148:107451] 第二步
2016-12-12 12:23:15.483 RACDemo[2148:107451] 第三步
2016-12-12 12:23:15.483 RACDemo[2148:107451] 完成

More

内存管理

在之前写的代码中,RAC的信号处理过程就像一个管道一样,ReactiveCocoa设计的一个目标就是支持匿名生成管道这种编程风格。那个这些管道是如何持有的呢?显然,它并没有分配某个变量或是属性,所以他也不会有引用计数的增加,那么它是怎么销毁的呢?

为了支持这种模型,RAC自己持有全局的所有信号,如果一个signal有一个或多个订阅者,那这个signal就是活跃的。如果所有的订阅者都被移除了,那这个信号就能被销毁了。

那么问题来了:如何取消订阅一个signal?在一个completed或者error事件之后,订阅会自动移除。你还可以通过RACDisposable 手动移除订阅。

@weakify和@strongify

RAC定义了@weakify和@strongify这两个宏,@weakify宏让你创建一个弱应用的影子对象(如果需要多个弱引用,可以传入多个变量),@strongify让你创建一个对之前传入@weakify对象的强引用。利用它们可以解决循环引用问题,如下代码:

    @weakify(self)
    [[self.textField.rac_textSignal
      map:^id(NSString *text) {
          return [text length] ?
          [UIColor whiteColor] : [UIColor yellowColor];
      }]
     
     subscribeNext:^(UIColor *color) {
         @strongify(self)
         self.textField.backgroundColor = color;
     }];

上面代码中subscribeNext:block中使用了self来获取textField的引用。block会捕获并持有其作用域内的值。因此,如果self和这个信号之间存在一个强引用的话,就有可能造成循环引用

总结

RAC要学习的内容还有很多,越往深学越觉得自己学得很肤浅,我这里只是稍微深入学习了一些基本概念和基本用法,还有其他很多高深用法还没有用到,等以后继续学习了在分享出来

相关文章

网友评论

    本文标题:ReactiveCocoa入门篇二

    本文链接:https://www.haomeiwen.com/subject/mipmmttx.html