美文网首页程序员
ReactiveCocoa详解

ReactiveCocoa详解

作者: 正直的瓜子脸 | 来源:发表于2018-02-02 15:51 被阅读0次

    一、RAC介绍

    RAC 是一个 iOS 中的函数式响应式编程框架,一般与MVVM配套使用。
    在非RAC开发中,都是习惯赋值。在RAC开发中,需要改变开发思维,由赋值转变为绑定,并不需要重写set方法。
    RAC项目的Podfile如下:

    use_frameworks!
    target '工程名称' do
    pod 'ReactiveObjC', '~> 3.0.0'
    end
    

    如果使用Swift作为开发语言,建议使用RXSwift。关于RXSwift,以后会单独写一篇文章介绍。

    RAC使用注意事项:
    1.RAC学习曲线陡峭,团队开发时要谨慎使用,确保项目组所有成员都会RAC,再使用RAC构建项目。如果一个人开发一个项目,要使用RAC,先跟项目经理商量一下,不要自己想当然的想用就用,这样如果公司要加人,会增加招人成本,公司不一定乐意。
    2.在使用过程中,尽量保持同一个项目的成员,代码风格一致,这样方便管理维护。
    3.关于学习曲线陡峭,有的同学可能不以为然,随便写个demo,体验了几个RAC基本操作方法,就觉得自己已经会用RAC了。其实不然,要真正的用RAC构造整个项目,再配上MVVM,是需要一个较长学习周期的。

    二、RAC常见用法

    • 代替代理
      rac_signalForSelector:用于替代代理
        // 需求:自定义redView,监听红色view中按钮点击
        // 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
        // rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
        // 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
        [[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);
    
        }];
    
    • 监听事件 (代替addTarget)
      rac_signalForControlEvents:用于监听某个事件。
    [[self.btn rac_signalForControlEvents:(UIControlEventTouchUpInside)]subscribeNext:^(__kindof UIControl * _Nullable x) {
            NSLog(@"%@",x);
        }];
    
    • 代替通知
      rac_addObserverForName:用于监听某个通知
    // 把监听到的通知转换信号
        [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
            NSLog(@"键盘弹出");
        }];
    
    • 监听文本框文字改变
      rac_textSignal:只要文本框发出改变就会发出这个信号
    [_textField.rac_textSignal subscribeNext:^(id x) {
           NSLog(@"文字改变了%@",x);
       }];
    

    三、常见类解释

    RactiveCocoa中很重要的两个class,一个是RACSignal,一个是RACSequence,而这两个class的super class就是RACStream。
    RACStream: 表示一个基本单元可以为任意值,其值会随着事件的变化而变化。可以在其上进行一些复杂的操作运算(map,filter,skip,take等.)此类不会被经常使用, 多情况下表现为signal和sequences(RACSignal 和RACSequence继承于RACStream类)。
    RACSiganl:只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。signal能发送3种不同类型的事件:Next,Completed,Error。
    信号三部曲:创建信号、订阅信号、发送信号。

    // 1.创建信号
        RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            // 2.发送信号,必须是一个对象类型
            [subscriber sendNext:@1];
            // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
            [subscriber sendCompleted];
            //return nil;
            return [RACDisposable disposableWithBlock:^{
                // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
                // 执行完Block后,当前信号就不在被订阅了。
                NSLog(@"信号被销毁");
            }];
        }];
    
        // 3.订阅信号,才会激活信号.
        [siganl subscribeNext:^(id x) {
            // block调用时刻:每当有信号发出数据,就会调用block.
            NSLog(@"接收到数据:%@",x);
        }];
    

    RACSubject: 信号提供者,自己可以充当信号,又能发送信号。

    // 1.创建信号
        RACSubject *subject = [RACSubject subject];
        // 2.订阅信号
        [subject subscribeNext:^(id x) {
            // block调用时刻:当信号发出新值,就会调用.
            NSLog(@"第一个订阅者%@",x);
        }];
        [subject subscribeNext:^(id x) {
            // block调用时刻:当信号发出新值,就会调用.
            NSLog(@"第二个订阅者%@",x);
        }];
        // 3.发送信号
        [subject sendNext:@"1"];
    

    RACReplaySubject:重复提供信号类,RACSubject的子类。
    RACDisposable:用于取消订阅或者清理资源,在一个completed或者error事件之后,就会自动触发它,订阅会自动移除。也可以通过RACDisposable 手动移除订阅。
    RACChannelTerminal :通道终端,代表 RACChannel 的一个终端,用来实现双向绑定。

    - (RACSignal *)bindTextField:(UITextField *)textField slider:(UISlider *)slider
    {
        //通道终端,代表 RACChannel 的一个终端,用来实现双向绑定。
        RACChannelTerminal *sliderChanel = [slider rac_newValueChannelWithNilValue:nil];
        RACChannelTerminal *textFieldChanel = [textField rac_newTextChannel];
        
        //双向绑定
        [[textFieldChanel map:^id _Nullable(id  _Nullable value) {
            return @([value floatValue]);
        }] subscribe:sliderChanel]; //输入框的值流向滑杆
        [[sliderChanel map:^id _Nullable(id  _Nullable value) {
            return [NSString stringWithFormat:@"%.2f", [value floatValue]];
        }] subscribe:textFieldChanel];  //滑杆的值流向输入框
        
        //merge合并信号,任何一个信号发送数据,都能监听到。
        return [textFieldChanel merge:sliderChanel];
    }
    
    //-----------------------------
    //功能实现:绑定滑杆和输入框,滑杆滑动输入框的内容跟随变化,输入框值改变,滑杆自动滑动到对应值位置。当三个滑杆或者输入框的值均改变过一次时,colorView的颜色作出改变。
    - (void)demo1
    {
        _redInput.text = _greenInput.text = _blueInput.text = @"0.5";
        
        RACSignal *redSignal = [self bindTextField:_redInput slider:_redSlider];
        RACSignal *greenSignal = [self bindTextField:_greenInput slider:_greeSlider];
        RACSignal *blueSignal = [self bindTextField:_blueInput slider:_blueSlider];
    
        //将多个信号合并起来,并且拿到各个信号最后一个值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
        RAC(_colorView, backgroundColor) = [[RACSignal combineLatest:@[redSignal, greenSignal, blueSignal]] map:^id _Nullable(RACTuple * _Nullable value) {
            return [UIColor colorWithRed:[value[0] floatValue]
                                   green:[value[1] floatValue]
                                    blue:[value[2] floatValue]
                                   alpha:1];
        }];
    }
    

    RACTuple:元组类,类似NSArray,用来包装值.
    RACSequence:RAC中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典,达到对值的一些过滤和转换。

    // 遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
        NSDictionary *dict = @{@"name":@"xmg",@"age":@18};
        [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
            // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
            RACTupleUnpack(NSString *key,NSString *value) = x;
            // 相当于以下写法
    //        NSString *key = x[0];
    //        NSString *value = x[1];
            NSLog(@"%@ %@",key,value);
        }];
    

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

    // 一、RACCommand使用步骤:
        // 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
        // 2.在signalBlock中,创建RACSignal,并且作为signalBlock的返回值
        // 3.执行命令 - (RACSignal *)execute:(id)input
        
        // 二、RACCommand使用注意:
        // 1.signalBlock必须要返回一个信号,不能传nil.
        // 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
        // 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
        // 4.RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。
        
        // 三、RACCommand设计思想:内部signalBlock为什么要返回一个信号,这个信号有什么用。
        // 1.在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
        // 2.当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。
        
        // 四、如何拿到RACCommand中返回信号发出的数据。
        // 1.RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
        // 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
        
        // 五、监听当前命令是否正在执行executing
        
        // 六、使用场景,监听按钮点击,网络请求
    
    // 1.创建命令
        RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
            NSLog(@"执行命令");
            // 创建空信号,必须返回信号
            //        return [RACSignal empty];
            // 2.创建信号,用来传递数据
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                [subscriber sendNext:@"请求数据"];
                // 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
                [subscriber sendCompleted];
                return nil;
            }];
        }];
    
        // 强引用命令,不要被销毁,否则接收不到数据
        _loginCommand = command;
    
    // 3.订阅RACCommand中的信号
        [command.executionSignals subscribeNext:^(id x) {
            //订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
            [x subscribeNext:^(id x) {
                NSLog(@"%@",x);
            }];
        }];
    
    // RAC高级用法
        // switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
       /* [command.executionSignals.switchToLatest subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];*/
    
    // 4.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。
        [[command.executing skip:1] subscribeNext:^(id x) {
           // executing:判断当前的block是否在执行,执行完之后会返回NO
            if ([x boolValue] == YES) {
                // 正在执行
                NSLog(@"正在执行");
            }else{
                // 执行完成
                NSLog(@"执行完成");
            }
        }];
    
       // 5.执行命令(控制器里执行此句代码)
        [self.login_vm.loginCommand execute:nil];
    

    RACMulticastConnection:用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。
    使用注意:RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建。
    RACScheduler:RAC中的队列,用GCD封装的。
    RACUnit :表⽰stream不包含有意义的值,也就是看到这个,可以直接理解为nil.

    四、RAC常用宏

    1、RAC 绑定一个信号
    RAC宏允许直接把信号的输出应用到对象的属性上 每次信号产生一个next事件,传递过来的值都会应用到该属性上

    //1、RAC 把一个对象的摸个属性绑定一个信号,只有发出信号,就会吧信号的内容给对象的属性赋值。
        //这里吧label的text属性绑定到textField改变信号中,textfield的内容发生改变的时候就会发出信号,只要文本框文字改变,就会修改label的文字。
        RAC(self.label,text) = _textfield.rac_textSignal;
    

    2、RACObserve 相当于kvo使用

    //person为一个模型,只要模型里面的name字段改变,就会把最新的name值显示在label上
    [RACObserve(self.person, name) subscribeNext:^(id  _Nullable x) {
            NSLog(@"%@",x);
            self.nameLabel.text = x;
        }];
    

    3、@weakify 和@strongify 解决循环引用,注意这两个宏是配套使用才有效。

    @weakify(self);
        [[self.testTextFileld rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
            NSLog(@"%@",x);
            @strongify(self);
            self.label.text = x;
        }];
    

    4、 RACChannelTo 用于双向绑定
    RACChannelTerminal也可以实现双向绑定,例子上面已写

    RACChannelTo(view, property) = RACChannelTo(model, property);
    

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

    // 把参数中的数据包装成元组
        RACTuple *tuple = RACTuplePack(@(1),@(30));
    

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

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

    五、常见操作方法

    所有的信号(RACSignal)都可以进行操作处理,因为所有操作方法都定义在RACStream.h中,因此只要继承RACStream就有了操作处理方法。

    1. bind(绑定) 核心方法

    ReactiveCocoa 操作的核心方法是 bind(绑定),而且也是RAC中核心开发方式。之前的开发方式是赋值,而用RAC开发,应该把重心放在绑定,也就是可以在创建一个对象的时候,就绑定好以后想要做的事情,而不是等赋值之后在去做事情。
    RAC底层都是调用bind, 在开发中很少直接使用 bind 方法,bind属于RAC中的底层方法,我们只需要调用封装好的方法,bind用作了解即可.

    2. flattenMap

    把源信号的内容映射成一个新的信号,信号可以是任意类型
    使用步骤:
    1.传入一个block,block类型是返回值RACStream,参数value
    2.参数value就是源信号的内容,拿到源信号的内容做处理
    3.包装成RACReturnSignal信号,返回出去

    //监听文本框的内容改变,重新映射成一个新的信号
    [[_textField.rac_textSignal flattenMap:^RACStream *(id value) {
        // block调用时机:信号源发出的时候
        // block作用:改变信号的内容
        // 返回RACReturnSignal (需要导入RACReturnSignal.h)
        return [RACReturnSignal return:[NSString stringWithFormat:@"信号内容:%@", value]];
    
    }] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    

    3. Map

    把源信号的值映射成一个新的值
    如果map的是一个sequence,那么会遍历里面的么一个元素,遍历的每一个值,都会做相同的处理

    //监听文本框的内容改变,映射成一个新值.
    [[_textField.rac_textSignal map:^id(id value) {
       //把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值。
        return [NSString stringWithFormat:@"信号内容: %@", value];
        
    }] subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    

    FlatternMap 和 Map 的区别

    FlatternMap 中的Block 返回信号。
    Map 中的Block 返回对象。
    开发中,如果信号发出的值 不是信号 ,映射一般使用 Map
    如果信号发出的值 是信号,映射一般使用 FlatternMap。

    信号中的信号(signalOfsignals)用flatternMap:

    // 创建信号中的信号
    RACSubject *signalOfsignals = [RACSubject subject];
    RACSubject *signal = [RACSubject subject];
    
    [[signalOfsignals flattenMap:^RACStream *(id value) {
     // 当signalOfsignals的signals发出信号才会调用
        return value;
    
    }] subscribeNext:^(id x) {
        // 只有signalOfsignals的signal发出信号才会调用,因为内部订阅了bindBlock中返回的信号,也就是flattenMap返回的信号。
        // 也就是flattenMap返回的信号发出内容,才会调用。
        NSLog(@"signalOfsignals:%@",x);
    }];
    
    // 信号的信号发送信号
    [signalOfsignals sendNext:signal];
    
    // 信号发送内容
    [signal sendNext:@"hi"];
    

    ------- 组合 -------

    组合就是将多个信号按照某种规则进行拼接,合成新的信号。

    4. concat

    按顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。

    //拼接信号 signalA、 signalB、 signalC
    
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"Hello"];
        [subscriber sendCompleted];
        return nil;
    }];
    
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"World"];
        [subscriber sendCompleted];
        return nil;
    }];
    
    RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"!"];
        [subscriber sendCompleted];
        return nil;
    }];
    
    // 拼接 A B, 把signalA拼接到signalB后,signalA发送完成,signalB才会被激活。
    RACSignal *concatSignalAB = [signalA concat:signalB];
    
    // A B + C
    RACSignal *concatSignalABC = [concatSignalAB concat:signalC];
    
    // 订阅拼接的信号, 内部会按顺序订阅 A->B->C
    // 注意:第一个信号必须发送完成,第二个信号才会被激活...
    [concatSignalABC subscribeNext:^(id x) {
        NSLog(@"%@", x);
    }];
    

    5. then

    用于连接两个信号,当第一个信号完成,才会连接then返回的信号。
    then方法会等待completed事件的发送,然后再订阅由then block返回的signal。这样就高效地把控制权从一个signal传递给下一个。
    注意使用then,之前信号的值会被忽略掉

    6. merge

    合并信号,任何一个信号发送数据,都能监听到。
    合并信号被订阅的时候,就会遍历所有信号,并且发出这些信号。
    合并信号一被订阅,就会订阅里面所有的信号。
    只要有一个信号被发出就会被监听。

    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
          [subscriber sendNext:@"A"];
          return nil;
      }];
    
      RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
          [subscriber sendNext:@"B"];
          return nil;
      }];
    
      // 合并信号, 任何一个信号发送数据,都能监听到
      RACSignal *mergeSianl = [signalA merge:signalB];
    
      [mergeSianl subscribeNext:^(id x) {
          NSLog(@"%@", x);
      }];
    //输出
    //A
    //B
    

    7. combineLatest

    将多个信号合并起来,并且拿到各个信号最后一个值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
    必须两个信号都发出内容,才会被触发, 并且把两个信号的 最后一次 发送的值组合成元组发出。

    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"A1"];
            [subscriber sendNext:@"A2"];
            return nil;
        }];
        
        RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"B1"];
            [subscriber sendNext:@"B2"];
            [subscriber sendNext:@"B3"];
            return nil;
        }];
        
        RACSignal *combineSianal = [signalA combineLatestWith:signalB];
        
        [combineSianal subscribeNext:^(id x) {
            NSLog(@"combineLatest:%@", x);
        }];
    /*
    输出
    2018-02-01 17:39:04.914804+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60400000fc70> (
        A2,
        B1
    )
    2018-02-01 17:39:04.915092+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60000000faa0> (
        A2,
        B2
    )
    2018-02-01 17:39:04.915270+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60000000fff0> (
        A2,
        B3
    )
    */
    
    //如果用[signalB combineLatestWith:signalA],那么结果就是(B3, A1)  (B3, A2)
    

    8. reduce

    把信号发出元组的值聚合成一个值
    reduce 合并信号的数据,进行汇总计算使用
    订阅聚合信号,每次有内容发出,就会执行reduceblcok,把信号内容转换成reduceblcok返回的值。
    常见的用法,(先组合在聚合)一般跟combineLatest 一起使用

    //判断用户名和密码同时存在的时候,按钮才能点击
    [[RACSignal combineLatest:@[self.nameField.rac_textSignal, self.passwordField.rac_textSignal] reduce:^id _Nullable(NSString *name, NSString *pwd){
            return @(name.length > 0 && pwd.length > 0);
        }] subscribeNext:^(id  _Nullable x) {
            _btn.enabled = [x boolValue];
        }];
    

    9. zip

    把两个信号压缩成一个信号,只有当两个信号 同时 发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件。
    1. 定义压缩信号,内部就会自动订阅signalA,signalB
    2. 每当signalA或者signalB发出信号,就会判断signalA,signalB有没有发出个信号,有就会把每个信号 第一次 发出的值包装成元组发出
    注意:combineLatest与zip用法相似,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。

    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"A1"];
            [subscriber sendNext:@"A2"];
            return nil;
        }];
        
        RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"B1"];
            [subscriber sendNext:@"B2"];
            [subscriber sendNext:@"B3"];
            return nil;
        }];
        
        RACSignal *zipSignal = [signalA zipWith:signalB];
        
        [zipSignal subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
    /*
    结果:
    2018-02-01 18:00:04.585460+0800 RAC 101[22558:30088028] <RACTwoTuple: 0x604000005840> (
        A1,
        B1
    )
    2018-02-01 18:00:04.585790+0800 RAC 101[22558:30088028] <RACTwoTuple: 0x6040000058d0> (
        A2,
        B2
    )
    */
    

    10. filter

    过滤信号,使用它可以获取满足条件的信号

    // 每次信号发出,会先执行过滤条件判断.
    [[_textField.rac_textSignal filter:^BOOL(NSString *value) {
        NSLog(@"原信号: %@", value);
        // 过滤 长度 <= 3 的信号
        return value.length > 3;
    }] subscribeNext:^(id x) {
        NSLog(@"长度大于3的信号:%@", x);
    }];
    

    11. ignore

    忽略某些信号
    底层调用了 filter 与 过滤值进行比较,若相等返回则 NO

    //过滤掉值为111的信号
    [[_textField.rac_textSignal ignore:@"111"] subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
    

    12. distinctUntilChanged

    当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。

    [[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
          NSLog(@"%@",x);
      }];
    

    13. skip

    跳过第N次的发送的信号。

    // 表示输入第一次,不会被监听到,跳过第一次发出的信号
    [[_textField.rac_textSignal skip:1] subscribeNext:^(id x) {
     NSLog(@"%@",x);
    }];
    

    14. take

    取前N次的发送的信号。

    RACSubject *subject = [RACSubject subject] ;
      // 取 前两次 发送的信号
      [[subject take:2] subscribeNext:^(id x) {
          NSLog(@"%@", x);
      }];
      [subject sendNext:@1];
      [subject sendNext:@2];
      [subject sendNext:@3];
    
      // 输出
      2017-01-03 17:35:54.566 ReactiveCocoa进阶[4969:1677908] 1
      2017-01-03 17:35:54.567 ReactiveCocoa进阶[4969:1677908] 2
    

    15. takeLast

    取最后N次的发送的信号
    前提条件,订阅者必须调用完成 sendCompleted,因为只有完成,就知道总共有多少信号.

    RACSubject *subject = [RACSubject subject] ;
      // 取 后两次 发送的信号
      [[subject takeLast:2] subscribeNext:^(id x) {
          NSLog(@"%@", x);
      }];
      
      [subject sendNext:@1];
      [subject sendNext:@2];
      [subject sendNext:@3];
      // 必须 跳用完成
      [subject sendCompleted];
    

    16. takeUntil

    获取信号直到某个信号执行完成

    // 监听文本框的改变直到当前对象被销毁
    [_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
    

    17. switchToLatest

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

    RACSubject *signalOfSignals = [RACSubject subject];
        RACSubject *signal = [RACSubject subject];
        
        // 获取信号中信号最近发出信号,订阅最近发出的信号。
        [signalOfSignals.switchToLatest subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
        
        [signalOfSignals sendNext:signal];
        [signal sendNext:@1];
    

    ---- 秩序 ----

    秩序包括 doNext 和 doCompleted 这两个方法,主要是在 执行sendNext 或者 sendCompleted 之前,先执行这些方法中Block。

    18. doNext

    执行sendNext之前,会先执行这个doNext的 Block

    19. doCompleted

    执行sendCompleted之前,会先执行这doCompletedBlock

    [[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"hi"];
            [subscriber sendCompleted];
            return nil;
            
        }] doNext:^(id x) {
            // 执行 [subscriber sendNext:@"hi"] 之前会调用这个 Block
            NSLog(@"doNext");
            
        }] doCompleted:^{
            // 执行 [subscriber sendCompleted] 之前会调用这 Block
            NSLog(@"doCompleted");
            
        }] subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
    
    /*
    运行结果:
    2018-02-02 09:56:39.206710+0800 RAC 101[40487:30848792] doNext
    2018-02-02 09:56:39.206954+0800 RAC 101[40487:30848792] hi
    2018-02-02 09:56:39.207112+0800 RAC 101[40487:30848792] doCompleted
    */
    

    ---- 线程 ----

    ReactiveCocoa 中的线程操作 包括 deliverOnsubscribeOn这两种,将 传递的内容 或 创建信号时 block中的代码 切换到指定的线程中执行。

    20. deliverOn

    内容传递切换到指定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用。

    // 在子线程中执行
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                NSLog(@"%@", [NSThread currentThread]);
                [subscriber sendNext:@123];
                [subscriber sendCompleted];
                return nil;
            }]
             deliverOn:[RACScheduler mainThreadScheduler]]
             subscribeNext:^(id x) {
                 NSLog(@"%@", x);
                 NSLog(@"%@", [NSThread currentThread]);
             }];
        });
    /*
    运行结果:
    2018-02-02 14:10:28.938587+0800 RAC 101[45182:31032478] <NSThread: 0x604000279c00>{number = 3, name = (null)}
    2018-02-02 14:10:29.076247+0800 RAC 101[45182:31031131] 123
    2018-02-02 14:10:29.076494+0800 RAC 101[45182:31031131] <NSThread: 0x60400006f540>{number = 1, name = main}
    */
    
    可以看到 副作用 在 子线程 中执行,而 传递的内容 在 主线程 中接收
    

    21. subscribeOn

    subscribeOn则是将 内容传递 和 副作用 都会切换到指定线程中。

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                NSLog(@"%@", [NSThread currentThread]);
                [subscriber sendNext:@123];
                [subscriber sendCompleted];
                return nil;
            }]
             subscribeOn:[RACScheduler mainThreadScheduler]] //传递的内容到主线程中
             subscribeNext:^(id x) {
                 NSLog(@"%@", x);
                 NSLog(@"%@", [NSThread currentThread]);
             }];
        });
    /*
    运行结果:
    2018-02-02 14:15:23.956524+0800 RAC 101[45319:31066198] <NSThread: 0x600000079940>{number = 1, name = main}
    2018-02-02 14:15:23.956737+0800 RAC 101[45319:31066198] 123
    2018-02-02 14:15:23.956961+0800 RAC 101[45319:31066198] <NSThread: 0x600000079940>{number = 1, name = main}
    */
    
    可以看到,内容传递 和 副作用 都切换到了 主线程 执行
    

    ----时间----

    时间操作就会设置信号超时,定时和延时。

    22. interval

    定时:每隔一段时间发出信号

    // 每隔1秒发送信号,指定当前线程执行
        [[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
            NSLog(@"定时:%@", x);
        }];
    

    23. timeout

    超时,可以让一个信号在一定的时间后,自动报错。

    RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            // 不发送信号,模拟超时状态
            // [subscriber sendNext:@"hello"];
            //[subscriber sendCompleted];
            return nil;
        }] timeout:1 onScheduler:[RACScheduler currentScheduler]];// 设置1秒超时
        
        [signal subscribeNext:^(id x) {
            NSLog(@"%@", x);
        } error:^(NSError *error) {
            NSLog(@"%@", error);
        }];
        
        // 执行代码 1秒后 输出:
        2017-01-04 13:48:55.195 ReactiveCocoa进阶[1980:492724] Error Domain=RACSignalErrorDomain Code=1 "(null)"
    

    24. retry

    重试:只要 发送错误 sendError: 就会重新执行创建信号的Block直到成功

    [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            if (i == 3) {
                [subscriber sendNext:@"Hello"];
            } else {
                // 发送错误
                NSLog(@"收到错误:%d", i);
                [subscriber sendError:nil];
            }
            i++;
            return nil;
        }] retry] subscribeNext:^(id x) {
            NSLog(@"%@", x);
        } error:^(NSError *error) {
            NSLog(@"%@", error);
        }];
    // 输出
    2017-01-04 14:36:51.594 ReactiveCocoa进阶[2443:667226] 收到错误信息:0
    2017-01-04 14:36:51.595 ReactiveCocoa进阶[2443:667226] 收到错误信息:1
    2017-01-04 14:36:51.595 ReactiveCocoa进阶[2443:667226] 收到错误信息:2
    2017-01-04 14:36:51.596 ReactiveCocoa进阶[2443:667226] Hello
    

    25. replay

    重放:当一个信号被多次订阅,反复播放内容

    RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@1];
            [subscriber sendNext:@2];
            return nil;
        }] replay];
        
        [signal subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
        
        [signal subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
    /*
    运行结果:
    2018-02-02 14:37:45.269758+0800 RAC 101[45938:31201667] 1
    2018-02-02 14:37:45.270018+0800 RAC 101[45938:31201667] 2
    2018-02-02 14:37:45.270175+0800 RAC 101[45938:31201667] 1
    2018-02-02 14:37:45.270300+0800 RAC 101[45938:31201667] 2
    /*
    

    26. throttle

    节流:当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。

    RACSubject *subject = [RACSubject subject];
        // 节流1秒,1秒后接收最后一个发送的信号
        [[subject throttle:1] subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
        [subject sendNext:@1];
        [subject sendNext:@2];
        [subject sendNext:@3];
        
        // 输出
        2018-02-02 14:53:00.234975+0800 RAC 101[46278:31282576] 3
    

    扩展:利用RAC封装网络请求

    通过RAC,我们可以奖网络请求封装为信号的形式,数据的获取可以通过订阅信号的方式来得到。与传统的网络请求相比,是不是感觉耳目一新?

    #import <AFNetworking/AFNetworking.h>
    #import "AFNetworking.h"
    #import <ReactiveObjC.h>
    
    @interface NetworkingManager : AFHTTPSessionManager
    + (instancetype)shareManager;
    - (RACSignal *)GET:(NSString *)url parameters:(id)parameters;
    @end
    
    --------------------------------------------------------------------
    @implementation NetworkingManager
    
    + (instancetype)shareManager
    {
        static NetworkingManager *manager;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            manager = [[self alloc]init];
            manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html" ,nil];
        });
        return manager;
    }
    
    //GET请求
    - (RACSignal *)GET:(NSString *)url parameters:(id)parameters
    {
        return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            [self GET:url parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                [subscriber sendNext:responseObject];
                [subscriber sendCompleted];
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                [subscriber sendError:error];
            }];
            return nil;
        }];
    }
    @end
    
    --------------------------------------------------------------------
    //请求网络数据
    - (void)fetchWeatherData
    {
        NetworkingManager *manager = [NetworkingManager shareManager];
        [[manager GET:@"http://www.weather.com.cn/data/sk/101010100.html" parameters:nil] subscribeNext:^(id  _Nullable x) {
            NSLog(@"%@", x);
        } error:^(NSError * _Nullable error) {
            NSLog(@"%@", error.localizedDescription);
        }];
    }
    //如上:调用请求,订阅信号后得到的x就是需要的json数据。
    

    参考文章:ReactiveCocoa进阶最快让你上手ReactiveCocoa之基础篇

    相关文章

      网友评论

        本文标题:ReactiveCocoa详解

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