ReactiveCocoa之ReactiveObjC快速入门

avatar
奇舞团移动端团队 @奇舞团

简介

ReactiveCocoa(RAC)RAC是一套基于CocoaFRP(Functional Reactive Programming:函数响应式编程)框架。

安装:可通过CocoaPods: pod "ReactiveObjC"

思想

学习一种新的编程范式,困难不在于一门编程语言,而是怎么学会用另一种方式去思考。思路比语法更重要。

FRP(Functional Reactive Programming)函数响应式编程,这是RAC的核心思想。FRP是一种编程范式,是组合了函数式编程和响应式编程范式。接下来熟悉一下这两个编程范式。

函数式编程

如何理解?

如果说面向对象是对数据的抽象,那么函数式编程就是对行为的抽象。

就是允许把函数本身作为参数传入另一个函数,还允许返回一个函数!它的核心是函数。在解决问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。

关键点: 参数是个函数 返回值也是函数

现实生活中行为:

洗车:可以将函数比作一个自动洗车间,脏的汽车进去,自动洗完后,出来还是汽车,只是汽车变干净了。

响应式编程

如何理解?

面向数据流的编程思想,类似观察者模式一样的表现。

计算是前后呼应的,相互关联,有些变化了,彼此之间的关系也会随着值的变化而变化。

现实生活中行为:

防火:室内烟雾散发到一定程度时,烟感探测器探测到安全极限,及时发出失火警报。

关键点: 相互关联、跟随变化

开始学习RAC

RAC中Signal的基本的订阅过程

  1. 创建信号

  2. 订阅信号

  3. 发送信号

  4. 取消信号

RAC常用的类

RACSignal

RACSignal(冷信号)继承于抽象类RACStream,是RAC的核心类,信号只有被订阅时才会送出信号值。

冷信号特点:

  • 不可变;
  • 被动;

创建冷信号

RACSignal.h中的声明的createSignal类方法如下:

+ (RACSignal<ValueType> *)createSignal:(RACDisposable * _Nullable (^)(id<RACSubscriber> subscriber))didSubscribe RAC_WARN_UNUSED_RESULT;

createSignal 类方法说明:

  • 作用:创建新的信号
  • 参数:didSubscribe,是一个block类型变量,didSubscribe block变量自身是一个类似C语言的函数指针变量,didSubscribe 指向的函数包含一个RACSubscriber 参数,返回一个RACDisposable 对象。
  • 返回值:返回一个RACSignal类型的信号。

例:

 //创建信号
  RACSignal *singalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    //发送信号
        [subscriber sendNext:@"1"];
        [subscriber sendNext:@(2)];
        [subscriber sendCompleted];
    
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"------this is RACDisposable---------");
        }];
    }];
    //订阅信号
    [singalA subscribeNext:^(id  _Nullable x) {
        NSLog(@"-------singalA value is %@-------",x);
    }];

返回结果如下:
2021-04-10 22:10:30.127793+0800 RACDemo[14148:238689] -------singalA value is 1-------
2021-04-10 22:10:30.127938+0800 RACDemo[14148:238689] -------singalA value is 2-------
2021-04-10 22:10:30.128098+0800 RACDemo[14148:238689] ------this is RACDisposable---------

上面的过程拆分一下可以这样写:

 //声明一名称为didSubscribe的block变量,带有一个RACSubscriber类型参数,返回一个RACDisposable类型对象
    RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber);
    //为didSubscribe定义,(block的实现)
    didSubscribe = ^RACDisposable * (id<RACSubscriber> sub){
      //发送信号
        [sub sendNext:@"1"];
        [sub sendNext:@(2)];
        [sub sendCompleted];
        [sub sendError:nil];
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"----didSubscribe--RACDisposable--------");
        }];
    };
    //创建信号
    RACSignal *signalB = [RACSignal createSignal:didSubscribe];
    //订阅信号
    [signalB subscribeNext:^(id  _Nullable x) {
        NSLog(@"------signalB x is %@--------",x);
    }];
返回结果如下:
2021-04-10 22:13:58.758873+0800 RACDemo[14258:241973] ------signalB x is 1--------
2021-04-10 22:13:58.758993+0800 RACDemo[14258:241973] ------signalB x is 2--------
2021-04-10 22:13:58.759078+0800 RACDemo[14258:241973] ----didSubscribe--RACDisposable-------

RACSubscriber

订阅者:负责发信号,本身是个协议;只要遵循它,并且实现协议内的方法,就会成为订阅者。

常用的几个方法:

//发送值到订阅者
- (void)sendNext:(nullable id)value;
//发送错误给订阅者
- (void)sendError:(nullable NSError *)error;
//告诉订阅者发送完成了
- (void)sendCompleted;

RACDisposable

取消订阅或清理资源,发送失败或者完成,会自动触发取消订阅方法。

//
+ (instancetype)disposableWithBlock:(void (^)(void))block;
//取消订阅
- (void)dispose;

RACSubject

RACSubject(热信号)t继承于RACSignal,一个subject可认为是一个信号,你可以手动控制sendNext、sendError、sendCompleted事件。可以帮助你将非RAC桥接到RAC上。

热信号特点

  • 可变;

  • 主动;

  • 可以桥接RAC和非RAC;

RACSubject 对外公开的接口只有两个,一个是创建一个热信号对象,另一个是发送消息的方法。

/// Returns a new subject.
+ (instancetype)subject;

// Redeclaration of the RACSubscriber method. Made in order to specify a generic type.
- (void)sendNext:(nullable ValueType)value;

创建热信号的接口:

+ (instancetype)subject;

这个方法创建的时候,会初始化两个私有属性,一个是subscribers数组 、另一个是disposable。

subscribers:数组里包含了所有的当前订阅者,在self的同步操作时候会使用它遍历所属的订阅者。

disposable:包含接收者对其他信号的所有订阅。主要是用于取消订阅和清理资源的操作。

发送信号的接口:

- (void)sendNext:(nullable ValueType)value;

重新定义了RACSubscriber协议的sendNext方法。

创建热信号

例:创建一个热信号

  //创建信号
    RACSubject *subject=[RACSubject subject];
    //订阅信号
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"subject x is [%@]",x);
    }];
    //发送信号
    [subject sendNext:@"123"];
    //结束订阅
    [subject sendCompleted];
返回结果如下:
  2021-04-13 16:48:52.043076+0800 RACDemo[29017:264320] subject x is [123]

常用高阶函数

绑定 bind

作用: 可将信号拦截后进行过滤再发送给新的信号,类似消息转发

typedef RACSignal * _Nullable (^RACSignalBindBlock)(ValueType _Nullable value, BOOL *stop);
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block RAC_WARN_UNUSED_RESULT;

例:

  //    //创建信号
     RACSubject *subject = [RACSubject subject];
    //绑定新的信号
    RACSignal *singnal=[subject bind:^RACSignalBindBlock _Nonnull {
        return ^RACSignal*(id value,BOOL *stop){
            return[RACReturnSignal return:value];
        };
    }];
    // 订阅信号
    [singnal subscribeNext:^(id  _Nullable x) {
        NSLog(@"bind x is [%@]",x);
    }];
    //发送信号
    [subject sendNext:@"send bind value 123"];
返回结果如下:
2021-04-12 10:16:57.562421+0800 RACDemo[4075:59403] bind x is [send bind value 123]

合并

concat

作用:按顺序合并多个信号为一个信号,其中前一个信号必须发送sendCompleted表示执行完成,才会执行后面一个信号,类似串行队列,先完成一个请求,才进行下一个请求。

- (RACSignal *)concat:(RACSignal *)signal RAC_WARN_UNUSED_RESULT;

例:

 //创建信号
    RACSubject *subject=[RACSubject subject];
    //创建信号
    RACSubject *subject2=[RACSubject subject];
    //连接信号
    RACSignal *signal=[subject concat:subject2];
    //订阅信号
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"concat x is [%@]",x);
    }];
    //发送信号
    [subject sendNext:@"abc"];
    //subject 信号执行完成,必须加上 sendCompleted,表示信号结束了,否则无法连接下一个信号
    [subject sendCompleted];
    //subject2发送信号
    [subject2 sendNext:@"ABC"];
返回结果如下:
2021-04-12 16:42:14.281303+0800 RACDemo[17888:262262] concat x is [abc]
2021-04-12 16:42:14.281532+0800 RACDemo[17888:262262] concat x is [ABC]

merge

作用:按顺序合并多个信号为一个信号,其中前一个信号不添加sendCompleted,也会执行后面一个信号。只要有一个信号来,合并操作就会接受。不强调先完成一个再进行下一个。

例:

      //创建信号
      RACSubject *subject=[RACSubject subject];
      //创建信号
      RACSubject *subject2=[RACSubject subject];
      //连接信号
      RACSignal *signal=[subject merge:subject2];
      //订阅信号
      [signal subscribeNext:^(id  _Nullable x) {
          NSLog(@"merge x is [%@]",x);
      }];
      //发送信号
      [subject sendNext:@"abc"];
      //subject2发送信号
      [subject2 sendNext:@"ABC"];
返回结果如下:
2021-04-12 17:08:23.410822+0800 RACDemo[19726:287122] merge x is [abc]
2021-04-12 17:08:23.411005+0800 RACDemo[19726:287122] merge x is [ABC]

combineLatest

作用:按顺序合并多个信号的最新值为元组

例:

 //创建信号
    RACSubject *subject = [RACSubject subject];
    //创建信号
    RACSubject *subject2 = [RACSubject subject];
    RACSignal *signal = [RACSignal combineLatest: @[subject, subject2]];
    //订阅信号
       [signal subscribeNext:^(id  _Nullable x) {
           NSLog(@"combineLatest x is  [%@]",x);
       }];
    //发送信号
    [subject sendNext:@"a"];
    //发送信号
    [subject2 sendNext:@"b"];
    //发送信号
    [subject2 sendNext:@"A"];
   //发送信号
    [subject sendNext:@"B"];
返回结果如下:
  2021-04-12 17:27:35.030304+0800 RACDemo[20891:303336] combineLatest x is  [<RACTuple: 0x600003af40b0> (
    a,
    b
)]
2021-04-12 17:27:35.030604+0800 RACDemo[20891:303336] combineLatest x is  [<RACTuple: 0x600003ae4ee0> (
    a,
    A
)]
2021-04-12 17:27:35.030805+0800 RACDemo[20891:303336] combineLatest x is  [<RACTuple: 0x600003ae85c0> (
    B,
    A
)]

映射

flattenMap

作用: 用于信号中信号,把源信号的内容映射成一个新的信号,信号是RACSignal类型

- (RACSignal *)flattenMap:(__kindof RACSignal * _Nullable (^)(ValueType _Nullable value))block RAC_WARN_UNUSED_RESULT;

例:映射值后,返回一下新的信号

//    //创建信号
    RACSubject *subject = [RACSubject subject];
    // 绑定信号,并返回映射后的值
    RACSignal * flattenMapSignal =[subject flattenMap:^__kindof RACSignal * _Nullable(id  _Nullable value) {
        value=[NSString stringWithFormat:@"flattenMapSignal映射后的值为:%@",value];
        return [RACReturnSignal return:value];
    }];
    //订阅信号
    [flattenMapSignal subscribeNext:^(id  _Nullable x) {
         NSLog(@"flattenMapSignal x is [%@]",x);
      }];
    //发送信号
    [subject sendNext:@"1234"];
返回结果如下:
2021-04-11 08:30:20.425283+0800 RACDemo[5943:81326] flattenMapSignal x is [flattenMapSignal映射后的值为:1234]

map

作用: 把源信号的值映射成一个新的值,可以是任意类型的

- (RACSignal *)map:(id _Nullable (^)(ValueType _Nullable value))block RAC_WARN_UNUSED_RESULT;

例:返回加工后的字符串

   //创建信号
        RACSubject *subject = [RACSubject subject];
        //绑定信号
    RACSignal *bindSignal = [subject map:^id _Nullable(id  _Nullable value) {
        //信号发送的内容
        NSString *str = [NSString stringWithFormat:@"映射 map 处理后的数据:%@", value];
        //返回任意类型的值
            return str;
        }];
        //订阅信号
        [bindSignal subscribeNext:^(id x) {
        NSLog(@"map x is [%@]",x);
        }];
        //发送数据
        [subject sendNext:@1];
返回结果如下:
  2021-04-11 08:34:34.676217+0800 RACDemo[6105:85742] map x is [映射 map 处理后的数据:1]

例:返回一个新的信号

 RACSubject *subject2=[RACSubject subject];
    //绑定信号
   RACSignal * bindSignal2=[subject2 map:^RACSignal * _Nullable(id  _Nullable value) {
       //映射后,返回一个新的信号对象
       value=[NSString stringWithFormat:@"修改value %@",value];
      return [RACReturnSignal return:value];
    }];
    //订阅信号
    [bindSignal2 subscribeNext:^(id  _Nullable x) {
        NSLog(@"返回值是信号  [%@]",x);
    }];
    //发送信号
    [subject2 sendNext:@(2)];
返回结果如下:
2021-04-11 08:41:03.927420+0800 RACDemo[6274:90310] 返回值是信号  [<RACReturnSignal: 0x600001e9d400> name: ]
    

过滤

filter

作用: 通过条件判断,过滤掉相应的信号

- (RACSignal<ValueType> *)filter:(BOOL (^)(ValueType _Nullable value))block RAC_WARN_UNUSED_RESULT;

例:过滤掉ABC开头的字符串

 //创建信号
    RACSubject *subject=[RACSubject subject];
    //过滤掉ABC开头的字符串
    RACSignal *signal= [subject filter:^BOOL(id  _Nullable value) {
        NSString *str=value;
        if ([str hasPrefix:@"ABC"]) {
            return NO;
        }else{
            return YES;
        }
    }];
    //订阅信号
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"过滤后的结果为 [%@]",x);
    }];
    //发送信号
    [subject sendNext:@"abc123"];
    //发送信号
    [subject sendNext:@"ABC123"];
返回结果如下:
2021-04-11 10:44:03.423107+0800 RACDemo[10384:185744] 过滤后的结果为 [abc123]

ignore

作用: 过滤信号值,忽略掉值为xxx的信号,按值忽略

- (RACSignal<ValueType> *)ignore:(nullable ValueType)value RAC_WARN_UNUSED_RESULT;

例:忽略掉值为ABC123的信号

 //创建信号
    RACSubject* subject=[RACSubject subject];
    //忽略掉值为ABC123的信号
    RACSignal *signal=[subject ignore:@"ABC123"];
    //订阅信号
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"ignore x is [%@]",x);
    }];
    //发送信号
    [subject sendNext:@"ABC123"];
    //发送信号
    [subject sendNext:@"abc123"];
返回结果如下:
2021-04-11 10:37:35.490486+0800 RACDemo[10205:180228] ignore x is [abc123]

then

- (RACSignal *)then:(RACSignal * (^)(void))block RAC_WARN_UNUSED_RESULT;

作用:忽略掉前一个信号发送的值,必须等前一个信号发送sendCompleted表示执行完成后,才执行下一个信号,否则无效,按信号忽略

例:忽略掉subject 信号,然后再执行subject2信号

    //创建信号
    RACSubject *subject = [RACSubject subject];
    //创建信号
    RACSubject *subject2 = [RACSubject subject];
    //忽略掉subject 信号
    RACSignal *signal = [subject then:^RACSignal * _Nonnull{
        return subject2;
    }];
    //订阅信号
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"then x is  [%@]",x);
    }];
    
    //发送信号
    [subject sendNext:@"a"];
    // 必须添加sendCompleted ,表示subject 信号执行完成,否则无效
    [subject sendCompleted];
    //发送信号
    [subject2 sendNext:@"b"];
    //发送信号
    [subject2 sendNext:@"A"];
返回结果如下:
2021-04-12 17:45:59.354229+0800 RACDemo[22106:321645] then x is  [b]
2021-04-12 17:45:59.354343+0800 RACDemo[22106:321645] then x is  [A]

take

作用: 按顺序获取第1次到n次信号

- (RACSignal<ValueType> *)take:(NSUInteger)count RAC_WARN_UNUSED_RESULT;

例:获取从第1~4次信号

    //创建信号
    RACSubject* subject=[RACSubject subject];
    //获取从第1~4次信号,若传0则不取信号
    RACSignal *signal=[subject take:4];
    //订阅信号
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"take x is [%@]",x);
    }];
    //发送信号
    [subject sendNext:@"ABC123"];
    //发送信号
    [subject sendNext:@"123ABC"];
    //发送信号
    [subject sendNext:@"abc123"];
    //发送信号
    [subject sendNext:@"123abc"];
    //发送信号
    [subject sendNext:@"123"];
    //发送信号
    [subject sendNext:@"abc"];
返回结果如下:
2021-04-11 10:59:05.655107+0800 RACDemo[10795:197326] take x is [ABC123]
2021-04-11 10:59:05.655298+0800 RACDemo[10795:197326] take x is [123ABC]
2021-04-11 10:59:05.655436+0800 RACDemo[10795:197326] take x is [abc123]
2021-04-11 10:59:05.655573+0800 RACDemo[10795:197326] take x is [123abc]

skip

作用: 跳过从第1次~n次 的信号

例:跳过从第1~3次信号

 //创建信号
    RACSubject* subject=[RACSubject subject];
    //跳过从第1~3次信号
    RACSignal *signal=[subject skip:3];
    //订阅信号
    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"skip x is [%@]",x);
    }];
    //发送信号
    [subject sendNext:@"123"];
    //发送信号
    [subject sendNext:@"abc"];
    //发送信号
    [subject sendNext:@"ABC"];
    //发送信号
    [subject sendNext:@"123abc"];
返回结果如下:
2021-04-11 11:18:20.866481+0800 RACDemo[11294:209783] skip x is [123abc]

switchToLatest

作用: 信号内的信号,获取信号内信号发送的最新的信号,当发送多个内部信号时候,只接受最新的内部信号

例:


    //创建信号
    RACSubject* subject=[RACSubject subject];
    //创建内部信号,用于subject内部的信号
    RACSubject * innerSubject=[RACSubject subject];
    //创建内部信号1,用于subject内部的信号
      RACSubject * innerSubject1=[RACSubject subject];
    //订阅信号,订阅subject内的即innerSubject信号
    [subject.switchToLatest subscribeNext:^(id  _Nullable x) {
        NSLog(@"switchToLatest x is [%@]",x);
    }];
    //subject发送innerSubject信号
    [subject sendNext:innerSubject];
    //subject发送innerSubject1信号
    [subject sendNext:innerSubject1];
    //通过innerSubject来发送信号
    [innerSubject sendNext:@"abc"];
   //通过innerSubject1来发送信号
    [innerSubject1 sendNext:@"123abc"];
返回结果如下:
2021-04-11 11:41:27.553097+0800 RACDemo[12001:229779] switchToLatest x is [123abc]

distinctUntilChanged

作用: 忽略重复的信号

例:执行后,忽略了重复的1

 //创建信号
    RACSubject* subject=[RACSubject subject];
    //订阅信号,只在订阅的信号与上一次不同时才会响应
    [subject.distinctUntilChanged subscribeNext:^(id  _Nullable x) {
        NSLog(@"distinctUntilChanged x is [%@]",x);
    }];
     //发送信号
    [subject sendNext:@"1"];
     //发送信号
    [subject sendNext:@"1"];
     //发送信号
     [subject sendNext:@"123"];
返回结果如下:
2021-04-11 11:53:14.833259+0800 RACDemo[12347:240090] distinctUntilChanged x is [1]
2021-04-11 11:53:14.833524+0800 RACDemo[12347:240090] distinctUntilChanged x is [123]

总结

RAC的高内聚,低耦合特性,很好的配合了MVVM的使用。因此如果现在正在使用MVVM模式开发,不妨试一下RAC框架。 以上是对RAC中的常用类、函数、宏的一些使用说明,并未对每个功能的底层原理进行分析,主要是介绍RAC是如何应用于业务中的。学习一项新的框架的步骤,一般是先知其所用,而后再知其所以然,后续再对某些功能进行详细的分析。