ReactiveCocoa快速上手

图片发自简书App

随着各种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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容