美文网首页
iOS ReactiveCocoa基础使用

iOS ReactiveCocoa基础使用

作者: 枫叶情结 | 来源:发表于2020-11-19 14:23 被阅读0次

    1、ReactiveCocoa简介

    ReactiveCocoa(简称为RAC),是由Github 开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程(FP)和响应式编程(RP)的特性。所以,RAC也被描述为函数响应式编程(FRP)框架。 RAC框架的核心类是RACSignal, 所有的消息都是通过信号的方式进行传递,可以简单的理解这些信号就是一连串的状态,在状态发生改变的时候,对应的订阅者就会收到通知,然后执行相应的操作。

    响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果。类似于蝴蝶效应,产生一个事件,会影响很多东西。这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。

    比如在程序开发中 a = b + c ,赋值之后 b 或者 c 的值变化后,a 的值不会跟着变化。而采用响应式编程,则如果 b 或者 c 的数值发生变化,a 的数值会同时发生变化,即 a 的值与 b 和 c 绑定。

    2、ReactiveCocoa主要应用场景

    UI数据绑定

    RAC可以方便的绑定任何数据流到UI控件上。

    用户交互事件绑定

    RAC可以为可交互的UI控件提供了一系列能发送Signal信号的方法。

    统一的消息传递机制

    RAC提供了统一的方法去处理异步的行为,包括delegate方法、blocks回调、target-action机制、notifications和KVO。

    3、ReactiveCocoa常见类

    思维导图

    ReactiveCocoa思维导图.png

    常见类

    1、RACSiganl

    信号类。只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。默认信号都是冷信号,也就是值改变了,也不会触发。只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。如何订阅信号:调用信号RACSignal的subscribeNext就能订阅

    RACEmptySignal :空信号,用来实现 RACSignal 的 +empty 方法;
    RACDynamicSignal :动态信号,使用一个 block 来实现订阅行为,我们在使用 RACSignal 的 +createSignal: 方法时创建的就是该类的实例;
    RACErrorSignal :错误信号,用来实现 RACSignal 的 +error: 方法;

    2、RACSubscriber

    表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过 createSignal 创建的信号,都有一个订阅者,帮助他发送数据。

    3、RACDisposable

    用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它,也可以手动调用 [disposable dispose] 触发。

    RAC使用流程

    1、创建信号

    2、订阅信号

    3、发送信号

    4、销毁信号 (需要的时候)

    示例如下:

    //1、创建信号(冷信号)
    RACSignal * single = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        //block调用时刻:每当有订阅者订阅信号,就会调用block
        
        //3、发送信号
        [subscriber sendNext:@"发送信息"];
        
        //如果不再发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅
        [subscriber sendCompleted];
        
        return [RACDisposable disposableWithBlock:^{
           //block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅。执行完Block后,当前信号就不在被订阅了
            NSLog(@"信号被销毁");
        }];
    }];
        
     //2、订阅信号(该信号变成热信号)
     RACDisposable *disposable = [single subscribeNext:^(id  _Nullable x) {
         //block调用时刻:每当有信号发送数据,就会调用该方法
         NSLog(@"接收到数据:%@",x);
     }];
        
     //主动触发取消订阅
     //[disposable dispose];
    

    4、RACSubject

    上面说到 RACSignal 是不具备发送信号的能力的,但是RACSubject这个类就可以做到订阅/发送为一体。

    使用场景:通常用来代替代理。

    示例如下:

    //1创建信号
    RACSubject * subject = [RACSubject subject];
        
    //2订阅信号 
    [subject subscribeNext:^(id  _Nullable x) {
       NSLog(@"%@",x);
    }];
       
    //3发送数据
    [subject sendNext:@"发送数据"];
    

    5、RACCommand

    RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。

    使用场景:监听按钮点击、网络请求等。

    示例如下:

    //1、创建命令
    RACCommand * loginBtnCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        NSLog(@"接收传过来的登录请求参数:%@",input);
                
        //创建信号
        return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
           NSLog(@"开始请求");
           NSLog(@"请求中...");
           NSLog(@"请求成功");
           NSLog(@"处理数据");
                    
           //发送信号
           [subscriber sendNext:@"我是请求到的数据"];
           //请求完成
           [subscriber sendCompleted];
                    
           return  [RACDisposable disposableWithBlock:^{
               NSLog(@"结束了");
           }];
        }];
    }];
            
    /*
    2、订阅loginBtnCommand命令事件中的信号发出的值
    switchToLatest表示的是最新发送的信号
    */
    [loginBtnCommand.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
         NSLog(@"接收到的请求数据:%@",x);
         NSLog(@"登录成功,跳转页面");
    }];
            
    /*
    3、监听loginBtnCommand监听命令是否执行完毕,默认会来一次,可以直接跳过
    默认会来一次,可以直接跳过,skip表示跳过第一次命令)
    */
    [[loginBtnCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
        if ([x boolValue]) {
            NSLog(@"正在执行中...");
        } else {
          NSLog(@"执行结束");
        }
    }];
            
    //4、执行命令
    [loginBtnCommand execute:@{@"account":@"1234567890",@"password":@"123456"}];
    

    6、RACSequence

    RAC中的集合类,用于代替NSArray、NSDictionary。

    使用场景:可以使用它来快速遍历数组和字典。

    示例如下:

    //遍历数组
    NSArray * arr = @[@"1",@"2",@"3",@"4",@"5"];
    /*
    第一步: 把数组转换成集合RACSequence             numbers.rac_sequence
    第二步: 把集合RACSequence转换RACSignal信号类    numbers.rac_sequence.signal
    第三步: 订阅信号,激活信号,会自动把集合中的所有值,遍历出来。  
    */
    [arr.rac_sequence.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"内容:%@",x);
    }];
    
    //遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
    NSDictionary *dict = @{@"name":@"xiaoming",@"age":@18};
    [dict.rac_sequence.signal subscribeNext:^(id x) {
         //解包元组(x是元祖)
         RACTupleUnpack(NSString *name,NSString *age) = x;
         NSLog(@"%@ %@",name,age);
    }];
    

    7、RACScheduler

    RAC中的队列,用GCD封装的。

    示例如下:

    //定时器
    @weakify(self)
    RACDisposable * disposable = [[RACSignal interval:2 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
       @strongify(self)
       NSLog(@"当前时间:%@",x);
       //关闭定时器
        [disposable dispose];
    }];
    

    8、RACMulticastConnection

    信号连接类。用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建。

    示例如下:

    /*
    RACMulticastConnection:信号连接类
    一般情况下,信号被订阅多少次,信号创建的block就被调用多少次。但是实际开发中,block只需调用一次就可以了。RACMulticastConnection可以实现不管订阅多少次信号,信号的block只调用一次。
         
    如下代码:信号被订阅了3次,但是singal里面的block只被调用一次,即只输出一次“请求数据”
    */
    //1、创建信号
    RACSignal * singal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
         NSLog(@"请求数据");
         [subscriber sendNext:@"请求数据"];
         return nil;
    }];
    
    //2、把信号转成连接类
    RACMulticastConnection * connection = [singal publish];
    
    //3、订阅连接类信号
    [connection.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"订阅1:%@",x);
    }];
    
    [connection.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"订阅2:%@",x);
    }];
    
    [connection.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"订阅3:%@",x);
    }];
    
    //4、连接,激活信号
    [connection connect];
    

    9、ReactiveCocoa常见宏

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

    //这里吧label的text属性绑定到textField改变信号中,textfield的内容发生改变的时候就会发出信号,label的text就会跟随着改变。
    RAC(self.label,text) = _textfield.rac_textSignal;
    

    2、RACObserve(self, name):监听某个对象的某个属性,返回的是信号。相当于kvo使用。

    //监听self的age属性,x为属性的信息
    [RACObserve(self, age) subscribeNext:^(id  _Nullable x) {
        NSLog(@"x:%@",x);
    }];
    

    3、RACTuplePack:把数据包装成RACTuple(元组类)

    4、RACTupleUnpack:把RACTuple(元组类)解包成对应的数据

    //元组
    RACTuple * tuple = RACTuplePack(@1,@2,@3);//使用RACTuplePack宏来快速创建
    NSLog(@"TUPLE:%@",tuple);
        
    //使用RACTupleUnpack宏快速解包
    RACTupleUnpack(NSNumber *num1,NSNumber *num2,NSNumber *num3) = tuple;
    NSLog(@"num1:%@",num1);
    NSLog(@"num2:%@",num2);
    NSLog(@"num3:%@",num3);
        
    //使用下标的方式来获取
    NSLog(@"第0个:%@",tuple[0]);
    

    10、 ReactiveCocoa常见用法

    1、rac_signalForSelector:用于替代代理。

    /*
    代替代理
    rac_signalForSelector:把调用某个对象的方法的信息转换成信号,调用这个方法,就会发送信号。
    当redView中的testButton按钮被点击了(即调用了testButtonClick),就会发出信号
    */
    [[redView rac_signalForSelector:@selector(testButtonClick:)] subscribeNext:^(id x) {
        NSLog(@"redView中的testButton按钮被点击了");
    }];
    

    2、rac_valuesAndChangesForKeyPath:代替KVO,用于监听某个对象的属性改变。

    /*
    KVO
    把监听redView的center属性改变转换成信号,只要值改变就会发送信号
    x为属性的信息
    */
    [[redView rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
        NSLog(@"center:%@",x);
    }];
    

    3、rac_signalForControlEvents:用于监听某个事件。

    /*
    监听事件
    把按钮点击事件转换为信号,点击按钮,就会发送信号
    x为Button的信息
    */
    [[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        NSLog(@"按钮被点击了");
    }];
    

    4、rac_addObserverForName:用于监听某个通知。

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

    5、rac_textSignal:监听文本框文字改变。

    //监听文本框的文字改变
    [self.passwordTextField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
          NSLog(@"密码框输入的内容%@",x);
    }];
    

    11、ReactiveCocoa组合用法

    1、concat:按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号

    //当需要按顺序执行的时候: 先执行C, 在执行D
    RACSubject *subC = [RACSubject subject];
    RACSubject *subD = [RACReplaySubject subject];
    
    NSMutableArray *array2 = [NSMutableArray array];
    
    //订阅信号
    [[subC concat:subD] subscribeNext:^(id  _Nullable x) {
         [array2 addObject:x];
     }];
    
    //发送信号
    [subD sendNext:@"D"];
    [subC sendNext:@"C"];
    [subC sendCompleted];
    
    //输出: [C, D]
    NSLog(@"%@", array2);
    

    2、then:用于连接两个信号,当第一个信号完成,才会连接then返回的信号

    RACSubject *subjectA = [RACReplaySubject subject];
    RACSubject *subjectB = [RACReplaySubject subject];
    
    //发送信号
    [subjectA sendNext:@"A"];
    [subjectA sendCompleted];
    [subjectB sendNext:@"B"];
    
    //订阅信号
    [[subjectA then:^RACSignal * _Nonnull{
         return subjectB;
    }] subscribeNext:^(id  _Nullable x) {
         NSLog(@"%@", x);
    }];
    
    //这里只会输出: B  不会输出: A
    

    3、merge:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用

    // 只要想无序的整合信号数据
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    RACSubject *subjectC = [RACSubject subject];
    
    //合并信号
    RACSignal *single = [[subjectA merge:subjectB] merge:subjectC];
    
    //订阅信号
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    //发出消息
    [subjectA sendNext:@"A"];
    [subjectC sendNext:@"C"];
    [subjectB sendNext:@"B"];
    //输出结果(分别输出): A, C, B
    

    4、zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件

    // 只要想无序的整合信号数据
    RACSubject *subjectA = [RACSubject subject];
    RACSubject *subjectB = [RACSubject subject];
    
    //合并信号
    RACSignal *single = [subjectA zipWith:subjectB];
    
    //订阅信号
    [single subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    //发出消息
    [subjectA sendNext:@"A"];
    [subjectB sendNext:@"B"];
    
    //输出: 元祖(A, B)
    

    5、combineLatest:将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号
    6、reduce(聚合):用于信号发出的内容是元组,把信号发出元组的值聚合成一个值

    @weakify(self)
    RAC(self.loginButton, enabled) = [RACSignal combineLatest:@[self.phoneTextField.rac_textSignal,self.passwordTextField.rac_textSignal] reduce:^id _Nonnull(NSString * account, NSString * password){
        @strongify(self)
        if (account.length && (password.length > 5)) {
            self.loginButton.backgroundColor = [UIColor blueColor];
            return @(YES);
         }else{
            self.loginButton.backgroundColor = [UIColor lightGrayColor];
            return @(NO);
         }
    }];
    

    7、filter:过滤,每次信号发出,会先执行过滤条件判断

    [[_accountText.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
         //类似手机号的输入, 只有等于11位的时候才返回true
         return value.length == 11;
    }] subscribeNext:^(NSString * _Nullable x) {
          //这里只会返回等于11位的字符
          NSLog(@"filter = %@", x);
    }];
    

    8、ignore:内部调用filter过滤,忽略掉ignore的值

    //当88时,下面的log不输出
    [[self.phoneTextField.rac_textSignal ignore:@"88"] subscribeNext:^(NSString * _Nullable x) {
         NSLog(@"ignore = %@", x);
    }];
    

    9、distinctUntilChanged:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。常用于UI刷新,即只有两次数据不一样才需要刷新UI。

    //创建信号
    RACSubject *subject = [RACSubject subject];
    
    //订阅
    [[subject distinctUntilChanged] subscribeNext:^(id  _Nullable x) {
         NSLog(@"distinctUntilChanged = %@", x);
    }];
    
    [subject sendNext:@12];
    [subject sendNext:@12];
    [subject sendNext:@23];
    
    /*
      输出结果:只会输出两次
      distinctUntilChanged = 12
      distinctUntilChanged = 23
    */
    

    10、take:从开始一共取N次的信号, 当遇到sendCompleted语句执行时, 会提前停止发送信号

    RACSubject *subject1 = [RACSubject subject];
    [[subject1 take:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    [subject1 sendNext:@1];
    [subject1 sendNext:@2];
    [subject1 sendNext:@3];
    
    //输出: 1, 2
    
    //如果上面发送信号的代码调整为
    [subject1 sendNext:@1];
    [subject1 sendCompleted];
    [subject1 sendNext:@2];
    [subject1 sendNext:@3];
    
    //那么输出结果将会,只输出: 1
    

    11、takeLast:取调用sendCompleted之前的N次信号,前提条件,订阅者必须调用sendCompleted,否则不会执行任何操作

    RACSubject *subject1 = [RACSubject subject];
    [[subject1 takeLast:2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    [subject1 sendNext:@1];
    [subject1 sendNext:@2];
    [subject1 sendNext:@3];
    [subject1 sendCompleted];
    
    /*
    输出:
      2
      3
    */
    

    12、takeUntil:只要传入的信号发送完成或者subject2开始发送信号的时候,就不会再接收信号的内容

    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];
    
    [[subject1 takeUntil:subject2] subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    [subject1 sendNext:@11];
    [subject1 sendNext:@12];
    //    [subject1 sendCompleted];
    [subject1 sendNext:@13];
    [subject2 sendNext:@"21"];
    [subject2 sendNext:@"22"];
    
    //输出: 11, 12, 13
    //当sendCompleted取消注释的时候, 只会输出: 11, 12
    

    13、skip:跳过N个信号后, 再开始订阅信号

    //创建信号
    RACSubject *subject = [RACSubject subject];
    
    //订阅信号(要求跳过2个信号)
    [[subject skip:2] subscribeNext:^(id  _Nullable x) {
         NSLog(@"%@", x);
    }];
    
    //发送信号
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];
    [subject sendNext:@4];
    
    //因为上面跳过了两个信号, 所以这里只会输出: 3, 4
    

    14、switchToLatest:用于signalOfSignals(信号的信号),有时候信号也会发出信号,会在signalOfSignals中,获取signalOfSignals发送的最新信号

    //信号的信号
    RACSubject *subject1 = [RACSubject subject];
    RACSubject *subject2 = [RACSubject subject];
    
    //获取信号中信号最近发出信号,订阅最近发出的信号
    [[subject1 switchToLatest] subscribeNext:^(id  _Nullable x) {
         NSLog(@"%@", x);
    }];
    
    //subject1发送subject2信号
    [subject1 sendNext:subject2];
    [subject2 sendNext:@"信号中信号"];
    
    //最终结果输出: "信号中信号"
    

    15、timeout:超时, 可以让一个信号在一定时间后自动报错

    //timeout: 超时, 可以让一个信号在一定时间后自动报错
    RACSignal *single = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            return nil;
    }] timeout:2 onScheduler:[RACScheduler currentScheduler]];
    
    [single subscribeNext:^(id  _Nullable x) {
            NSLog(@"%@", x);
    } error:^(NSError * _Nullable error) {
            //2秒后自动调用
            NSLog(@"%@", error);
    }];
    

    12、注意

    1、系统要求:ReactiveCocoa 要求 OS X 10.8+ 以及 iOS 8.0+。

    2、强引用问题

    @weakify 和@strongify 这个两个宏是为了解决在使用block的时候强引用问题,注意这两个宏成对使用才有效。

    @weakify(self)
    [[self.forgotPasswordButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
         @strongify(self)
         //发送数据
         [self.forgotPasswordButtonRACSubject sendNext:nil];
    }];
    

    相关文章

      网友评论

          本文标题:iOS ReactiveCocoa基础使用

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