美文网首页iOS开发数据传递
ReactiveCocoa快速上手

ReactiveCocoa快速上手

作者: 楼上那位 | 来源:发表于2016-02-18 22:51 被阅读416次
    ReactiveCocoa快速上手

    随着各种Rx的开始流行,做IT的码农门不得不学习各种新的知识,不然没有逼格,没办法在老板面前提涨工资啊啊啊啊啊。所以这篇文章我就记录一下我学习ReactiveCocoa的常见方法,作参考使用。
    在ReactiveCocoa中,关键是理解Signal流,所有的信息传递都是以流的形式

    常见的宏定义

    RAC(TRAGET,[KEYPATH,[NIL_VALUE]])

    RAC(self.outputLabel, text) = self.inputTextField.rac_textSignal;
    RAC(self.outputLabel, text, @"收到nil时就显示我") = self.inputTextField.rac_textSignal;
    

    这个宏是最常用的,RAC()总是出现在等号的左边,右边是一个signal,表示将一个signal和一个对象的属性绑定,signal每一次更新都会自动执行:[TARGET setValue:value ?: NUL_VALUE forkeyPath:KEYPATH]

    RACObserver(TARGET,KEYPATH) 产生一个signal,当keypath有改变就更新。例如:

    RAC(self.outputLabel, text) = RACObserve(self.model, name);
    

    当model的name属性发生改变都会反映到label的text上
    还有另外两个宏
    @weakify(self) , @strongify(self) 二者需要成对出现,先weak 后strong

    创建信号
       RACSignal * signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"0000");
           [subscriber sendNext:@1];// 如果 注释掉subscribeNext就不会发送
            NSLog(@"1111");
            [subscriber sendCompleted];
            NSLog(@"2222");
            return [RACDisposable disposableWithBlock:^{       
                // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。            
                // 执行完Block后,当前信号就不在被订阅了。            
                NSLog(@"信号被销毁");
            }];
        }];
        
    

    信号只有被订阅才能触发

      // 3.订阅信号,才会激活信号.
        [signal subscribeNext:^(id x) {
            // block调用时刻:每当有信号发出数据,就会调用block.
            NSLog(@"接收到数据:%@",x);
        }];
        
        [signal subscribeNext:^(id x) {
            // block调用时刻:每当有信号发出数据,就会调用block.
            NSLog(@"2接收到数据:%@",x);
        }];
      
    

    注意:这里有一个副作用,每次subscribeNext 就会触发一次subscriber的block。
    0000 1111 2222 被调用两次 可以使用RACMulticastConnection 进行解决

    RACObserver(owner,Property)
      RACSignal * signal = [RACObserve(self, model.name) map:^id(id value) {
            NSLog(@"value =%@",value);
            return value ? @YES : @NO;
        }];
        
        [signal subscribeNext:^(id x) {
            NSLog(@"xxxxxx =%@",x);
        }];
        
    

    使用KVO,监听属性的变化,值的任何改变都会反映出来
    我们可以看一下源代码:

    #define RACObserve(TARGET, KEYPATH) \\
        ({ \\
            _Pragma("clang diagnostic push") \\
            _Pragma("clang diagnostic ignored \\"-Wreceiver-is-weak\\"") \\
            __weak id target_ = (TARGET); \\
            [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \\
            _Pragma("clang diagnostic pop") \\
        })
    

    rac_valueForKeyPath: observer:该方法的实现是在NSObject (RACPropertySubscribing)类中

    RACSubject

    既可以充当订阅者也可以充当信息发送者

     RACSubject *subject = [RACSubject subject];
        
        // 2.订阅信号
        [subject subscribeNext:^(id x) {
        
            NSLog(@"第一个订阅者%@",x);
        }];
        [subject subscribeNext:^(id x) {
          
            NSLog(@"第二个订阅者%@",x);
        }];
        
        // 3.发送信号
        [subject sendNext:@"100"];
         [subject sendNext:@"200"];
    

    这里也是需要注意的是:每次subscribeNext都会产生一个subscriber
    打印输出如下:

    2016-02-18 14:05:18.030 JFReactive[19899:1936775] begin
    2016-02-18 14:05:18.030 JFReactive[19899:1936775] subscribers = <RACPassthroughSubscriber: 0x7f8be0ec5730>
    2016-02-18 14:05:18.031 JFReactive[19899:1936775] 第一个订阅者100
    2016-02-18 14:05:18.031 JFReactive[19899:1936775] subscribers = <RACPassthroughSubscriber: 0x7f8be0eaf310>
    2016-02-18 14:05:18.031 JFReactive[19899:1936775] 第二个订阅者100
    2016-02-18 14:05:18.031 JFReactive[19899:1936775] end
    2016-02-18 14:05:18.031 JFReactive[19899:1936775] begin
    2016-02-18 14:05:18.031 JFReactive[19899:1936775] subscribers = <RACPassthroughSubscriber: 0x7f8be0ec5730>
    2016-02-18 14:05:18.032 JFReactive[19899:1936775] 第一个订阅者200
    2016-02-18 14:05:18.032 JFReactive[19899:1936775] subscribers = <RACPassthroughSubscriber: 0x7f8be0eaf310>
    2016-02-18 14:05:18.032 JFReactive[19899:1936775] 第二个订阅者200
    2016-02-18 14:05:18.032 JFReactive[19899:1936775] end
    

    RACSubject 实现代理:

    RACSubject实现代理
    RACReplaySubject

    重复发送之前保存的数据

     // 1.创建信号
        RACReplaySubject *replaySubject = [RACReplaySubject subject];  
        // 2.发送信号
        [replaySubject sendNext:@1];
        [replaySubject sendNext:@2]; 
        // 3.订阅信号
        [replaySubject subscribeNext:^(id x) {// 创建第一个subscriber        
            NSLog(@"第一个订阅者接收到的数据%@",x);
        }];   
        // 订阅信号
        [replaySubject subscribeNext:^(id x) {// 创建第二个subscriber       
            NSLog(@"第二个订阅者接收到的数据%@",x);
        }];
        [replaySubject sendCompleted];//
    

    输出如下:

      如果sendNext发生在 subscribeNext之前就打印如下
         2015-12-26 17:29:07.467 JFReactive[60987:2909146] 第一个订阅者接收到的数据1
         2015-12-26 17:29:07.467 JFReactive[60987:2909146] 第一个订阅者接收到的数据2
         2015-12-26 17:29:07.468 JFReactive[60987:2909146] 第二个订阅者接收到的数据1
         2015-12-26 17:29:07.468 JFReactive[60987:2909146] 第二个订阅者接收到的数据2
         
         如果是发生在之后
         2016-01-04 16:46:28.693 JFReactive[32869:4260996] 第一个订阅者接收到的数据1
         2016-01-04 16:46:28.693 JFReactive[32869:4260996] 第二个订阅者接收到的数据1
         2016-01-04 16:46:28.694 JFReactive[32869:4260996] 第一个订阅者接收到的数据2
         2016-01-04 16:46:28.696 JFReactive[32869:4260996] 第二个订阅者接收到的数据2
    
    序列 Sequence

    一般情况下我们遍历一个序列使用for循环,现在有了RAC,简单了很多哦

    NSArray *numberSequnence = @[@1,@2,@3,@4];
    [numberSequnence.rac_sequence.signal subscribeNext:^(id x) {
            
            NSLog(@"thread = %@",[NSThread currentThread]);
            NSLog(@"main thread = %@",[NSThread mainThread]);
            NSLog(@"%@",x);
        }];
    

    输出如下:

    2016-02-18 15:49:25.223 JFReactive[21263:1981822] create subscribers = <RACSubscriber: 0x7f916af19160>
    2016-02-18 15:49:25.225 JFReactive[21263:1982967] thread = <NSThread: 0x7f916ad97680>{number = 2, name = (null)}
    2016-02-18 15:49:25.226 JFReactive[21263:1982967] main thread = <NSThread: 0x7f916ad05ca0>{number = 1, name = (null)}
    2016-02-18 15:49:25.226 JFReactive[21263:1982967] 1
    2016-02-18 15:49:25.227 JFReactive[21263:1982967] thread = <NSThread: 0x7f916ad97680>{number = 2, name = (null)}
    2016-02-18 15:49:25.227 JFReactive[21263:1982967] main thread = <NSThread: 0x7f916ad05ca0>{number = 1, name = (null)}
    2016-02-18 15:49:25.227 JFReactive[21263:1982967] 2
    2016-02-18 15:49:25.227 JFReactive[21263:1982967] thread = <NSThread: 0x7f916ad97680>{number = 2, name = (null)}
    2016-02-18 15:49:25.228 JFReactive[21263:1982967] main thread = <NSThread: 0x7f916ad05ca0>{number = 1, name = (null)}
    2016-02-18 15:49:25.228 JFReactive[21263:1982967] 3
    2016-02-18 15:49:25.228 JFReactive[21263:1982967] thread = <NSThread: 0x7f916ad97680>{number = 2, name = (null)}
    2016-02-18 15:49:25.228 JFReactive[21263:1982967] main thread = <NSThread: 0x7f916ad05ca0>{number = 1, name = (null)}
    2016-02-18 15:49:25.228 JFReactive[21263:1982967] 4
    

    我们会发现遍历序列是开启了一个线程,并不在主线程
    接下来我们看看遍历字典的例子

     NSDictionary *dict = @{@"name":@"sun",@"age":@18};
        [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
            
            // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
            RACTupleUnpack(NSString *key,NSString *value) = x;
            NSString *keys = x[0];
            NSString *values = x[1];
            
            NSLog(@"%@ %@",key,value);
            NSLog(@"thread = %@",[NSThread currentThread]);
            NSLog(@"main thread = %@",[NSThread mainThread]);
            
        }];
    

    同样,也不在主线程

    Command

    command 手动执行需要excute方法,但是和button绑定后不需要手动执行,因为button有相应的rx_command
    在这里引用一篇blog中的说明

     // 一、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(@"执行命令");
            NSLog(@"input =%@",input);
            // 创建空信号,必须返回信号
            // return [RACSignal empty];
            
            // 2.创建信号,用来传递数据
            RACSignal * rSignal =[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                
                [subscriber sendNext:@"请求数据"];
                
                // 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
                [subscriber sendCompleted];
                
                return nil;
            }];
            NSLog(@"innerSiganl = %@",rSignal);
            return rSignal;
            
        }];
        
        // 强引用命令,不要被销毁,否则接收不到数据
        _command = command;
        
        // 3.订阅RACCommand中的信号
        //RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
        // 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
        
        [command.executionSignals subscribeNext:^(id x) {
            
             NSLog(@"subscribe signal  %@",x);//返回signal
            [x subscribeNext:^(id x) {
                
             NSLog(@"singnal's next  %@",x);//返回signal的数据
            }];
            
        }];
          // 4.监听命令是否执行完毕,默认会先触发 正在执行 x= yes,可以直接跳过,skip表示跳过第一次信号。
        [[command.executing skip:1] subscribeNext:^(id x) {
            // skip 跳过外部的信号的执行处理,只处理command内部信号,所以只执行了2次 1 ,0
            
            NSLog(@"excuting skip  xx = %@",x);
            if ([x boolValue] == YES) {
                // 正在执行
                NSLog(@"正在执行");
                
            }else{
                // 执行完成
                NSLog(@"执行完成");
            }
            
        }];
        [command.executing subscribeNext:^(id x) {
            NSLog(@"excuting next :%@",x);// 输出 0 1 0  三次
        }];
        
        // 5.执行命令
        [self.command execute:@100];// 无此语句则无法触发所有的command
    

    从上述描述中可知 executionSignals 是一个信号中的信号
    还有其他的方法:
    switchToLatest 切换到最新的信号
    skip(count)忽略count个信号
    输出结果:

     2015-12-26 18:48:23.728 JFReactive[61623:2932153] excuting next :0
         2015-12-26 18:48:23.728 JFReactive[61623:2932153] 执行命令
         2015-12-26 18:48:23.728 JFReactive[61623:2932153] input =100
         2015-12-26 18:48:23.729 JFReactive[61623:2932153] excuting xx = 1
         2015-12-26 18:48:23.729 JFReactive[61623:2932153] 正在执行
         2015-12-26 18:48:23.730 JFReactive[61623:2932153] excuting next :1
         2015-12-26 18:48:23.730 JFReactive[61623:2932153] subscribe signal  <RACDynamicSignal: 0x7fa053437f20> name:
         2015-12-26 18:48:23.730 JFReactive[61623:2932153] singnal's next  请求数据
         2015-12-26 18:48:23.730 JFReactive[61623:2932153] switchToLast subnext: 请求数据
         2015-12-26 18:48:23.731 JFReactive[61623:2932153] excuting xx = 0
         2015-12-26 18:48:23.731 JFReactive[61623:2932153] 执行完成
         2015-12-26 18:48:23.731 JFReactive[61623:2932153] excuting next :0
    
    Map 和flattenMap

    FlatternMap和Map的区别
    1.FlatternMap中的Block返回信号。
    2.Map中的Block返回对象。
    3.开发中,如果信号发出的值不是信号,映射一般使用Map
    如果信号发出的值是信号,映射一般使用FlatternMap。
    flattenMap 在map的基础上使其flatten,也就是当Signal嵌套(一个Signal的事件是另一个Signal)的时候,会将内部Signal的事件传递给外部Signal

    RACMulticastConnection

    解决signal 每次订阅subscribeNext 就出发一次 sendNext

     RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"发送请求");
            [subscriber sendNext:@"200"];
            return nil;
        }];
     // 使用muticastConnection 替代 解决 了多个订阅,发多次请求的问题
        RACMulticastConnection *connect = [signal publish];
        
        // 3.订阅信号,
        // 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
         //
        
        [connect.signal subscribeNext:^(id x) {
            
            NSLog(@"订阅者一信号 %@",x);
            
        }];
        
        [connect.signal subscribeNext:^(id x) {
            
            NSLog(@"订阅者二信号 %@",x);
            
        }];
        
        // 4.连接,激活信号
       [connect connect];
    

    打印输出:

    2016-02-18 22:00:30.238 JFReactive[24275:2098877] 发送请求
    2016-02-18 22:00:30.238 JFReactive[24275:2098877] 订阅者一信号 200
    2016-02-18 22:00:30.238 JFReactive[24275:2098877] 订阅者二信号 200
    
    rac_liftSelector
    #pragma mark 多个信号统一处理 初始时必须两个信号均发送成功,然后任意信号发送都会触发,发送error 都不会触发selector
    - (void)multiSignalWithSelector{
        // 6.处理多个请求,都返回结果的时候,统一做处理.
        RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            // 发送请求1
           [subscriber sendNext:@"发送请求1"];
            
            //[subscriber sendError:nil];
            return nil;
        }];
        
        RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            // 发送请求2
            [subscriber sendNext:@"发送请求2"];
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [subscriber sendNext:@"发送请求2"];
            });
            return nil;
        }];
        
        // 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
        [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
    }
    
    - (void)updateUIWithR1:(id)data r2:(id)data1
    {
        NSLog(@"更新UI%@  %@",data,data1);
    }
    
    bind

    该方法适用于提前终止绑定或者覆盖状态,否则最好使用flattenMap

     [[_textField.rac_textSignal bind:^RACStreamBindBlock{
            
            return ^RACStream *(id value, BOOL *stop){
                
                //当信号有新的值发出,就会来调用这个block。
                NSLog(@"%d",*stop);
                if ([value isEqualToString:@"fff"]) {
                    *stop = YES;
                }
                return [RACReturnSignal return:[NSString stringWithFormat:@"value:%@",value]];
            };  
        }] subscribeNext:^(id x) {
            
            NSLog(@"输出XXX %@",x);///当stop = yes 后不再输出
            
        }];
    

    参考:
    http://www.cnblogs.com/sunnyxx/p/3544703.html

    http://www.jianshu.com/p/87ef6720a096

    RACCommand例子:
    http://www.tuicool.com/articles/nYJRvu

    相关文章

      网友评论

        本文标题:ReactiveCocoa快速上手

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