首页 > 代码库 > Reactive Cocoa 响应式编程开发实例讲解-中篇

Reactive Cocoa 响应式编程开发实例讲解-中篇

 

       上一篇文章作为开门篇讲述了Cocoa Reactive概述。


       这里我们详细介绍一下CocoaReative在代码中的应用。

       网上好多blog有人形容CocoaReative 中 signals是插座或者水龙头,感觉不是很好理解。我举个更贴近生活的,用电话订菜(餐馆是Signals,电话订阅是SubScriberNext)。

       1.概述

         Create一个Signal我们视为是一个支持电话订餐的餐馆,他们有很多菜,油盐酱醋就更不用说,当一个电话打进来首先,这个Signal就开始执行,等菜做好了,菜馆要做的是SendNext通知你一下,你呢?就出来拿,或者让他们送来,最后吃掉。分析一下在这期间,可能会有好多订阅者,菜馆只有一个,厨师会源源不断的炒菜(不要说什么如果菜馆没菜怎么办?太坑了),满足所有订阅者(电话预定)的需求。当然如果没人订阅这些配菜不会被炒,都处于配菜状态,所以,这些配菜可以用来组合,过滤什么的,以达到满足用户的口味(SubscriberNext)。怎么样这个举例不知道能否帮助你的理解。

        后面的讲述将会拆解步骤来辅助大家理解不同的功能和类、方法。

    //我是菜馆,有了我你们才有源源不断的饭菜可以吃
    RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        NSLog(@"接单了");
        NSLog(@"处理.....");
        [subscriber sendNext:@"炒菜完成,打电话给客户"];
        return nil;
    }];
    
    //有菜馆的菜单了,开始电话预定
    [signal subscribeNext:^(id x){
        NSLog(@"客户处理开始吃了:X是菜馆给的%@",x);
    }];

       这样不知道大家是否明白,反正我是明白了。提一个问题,如果没有电话预定,菜馆能做菜吗?(Signal能触发吗?)答案是:如果不怕赔钱,可以天天做好菜等着(估计没人吃,几天你的菜馆就关门了)。


       2.Signals和它的两种状态-冷、热信号(其实就是菜馆有没有生意)

         如果菜馆没有接到电话约定是不是生意很冷啊,所以叫做冷信号。如果不停的接单是不是生意红火啊,叫(红火不就是热吗)做热信号。那放到程序中怎么理解:

    //我是菜馆,有了我你们才有源源不断的饭菜可以吃
    RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        NSLog(@"接单了");
        NSLog(@"处理.....");
        [subscriber sendNext:@"炒菜完成,打电话给客户"];
        //完成
        [subscriber sendCompleted];
        return nil;
    }];
    
    //如果后面没有订单我的生意怎么做啊!

        一个Signal使用CreateSignal创建,这里没什么好讲的记住就可以了。

        上面已经说了,如果没有订单我的生意怎么做,这就是冷Signal。

        突然有一天,来生意了,结果来了一百个。是不是就火了,这就是热Signal。        

    //来生意了
    //有菜馆的菜单了,开始电话预定
    [signal subscribeNext:^(id x){
        NSLog(@"客户处理开始吃了");
    }];

        到这里不知道到家是否有对Signal有个初步的认识,Signal是Reactive的核心,理解了,基本上你就可以驾驭这个框架做你想做的。它不仅仅有SendNext方法,还有

         sendNext:其实生活中菜馆是把所有的菜做好了,才打电话通知你,程序中是这样的,超好一个菜就sendNext:客户就吃一个。明白了很像在饭店吃饭(对,就是这样,你就理解是在饭店吃饭(执行Signal),做好一个上一个(SendNext),你吃一个(subscribeNext:))。

         sendError: 这个就不用说了,肯定是不好意思,给你炒菜炒错了,现实中,你会想算了,吃吧(是你对错误的处理方式),这个方式只能执行一次(要是多长你还不要了厨师的命啊)。

         sendComplete:这个也是执行一次,结账走人了。

    //执行一次
    [signal subscribeCompleted:^{
        NSLog(@"结账走人");
    }];
    
    //执行一次
    [signal subscribeError:^(NSError *error) {
        NSLog(@"算了,人家做生意也不容易,将就吃吧");
    }];

        这个算是响应式编程吧。大家自己体会,体会不了?不是吧,再举个例子,大家还记我们支付宝在输入密码钱,确定按钮是灰色的对吧,一旦我们输完密码,按钮高亮,这个就是一种信号的响应。不多说了!

       3.doNext(打包送一次性筷子)

    //我是菜馆,有了我你们才有源源不断的饭菜可以吃
    RACSignal *signal = [[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        NSLog(@"接单了");
        NSLog(@"处理.....");
        [subscriber sendNext:@"炒菜完成,打电话给客户"];
        //完成
        [subscriber sendCompleted];
        return nil;
    }] doNext:^(id x) {
        NSLog(@"给点一次筷子什么的,米饭啊");
    }];
    <pre name="code" class="html">    //有菜馆的菜单了,开始电话预定
[signal subscribeNext:^(id x) { NSLog(@"客户处理开始吃了"); }];

       doNext 是在信号被处理前,做一些同步修饰工作,比如说送一次性筷子,哈哈,比如你在登陆Signal送出去后,你希望按钮(或者转菊花)现实登陆中....,都可以在这里做掉的。

      4.对信号Signal数据的过滤和组合(就是我不吃辣椒,我不吃香菜什么的)

       菜馆在做菜的时候肯定都是配好菜的,之后把这些配菜组合过滤,就变成了我们想吃的菜,有人问如何过滤?你喜欢吃香菜吗?喜欢辣椒吗?喜欢大蒜吗?这回知道了吧?那如何反映到Reactivie中呢?

       

    RACSignal *signal = [[[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        NSLog(@"接单了");
        NSLog(@"处理.....");
        [subscriber sendNext:@"炒菜完成,打电话给客户"];
        //完成
        [subscriber sendCompleted];
        return nil;
    }] filter:^BOOL(id value) {                    //-----------------------------------------新增代码
        //过滤辣椒   
        BOOL noLaJiao = YES;
        NSLog(@"过滤辣椒");
        return noLaJiao;
    }] doNext:^(id x) {
        NSLog(@"给点一次筷子什么的,米饭啊");
    }];
    
    //订单-<span style="font-family: Arial, Helvetica, sans-serif;">--------------------------------------------------------------如果noLaJiao=NO;你会吃吗?</span>    [signal subscribeNext:^(id x) {
        NSLog(@"客户处理开始吃了");
    }];

       上面代码有什么不同,你一眼就看出来,过滤辣椒,你可以把这个代码copy过去运行一下,你把noLaJiao改成NO试试,你看看订单那部分代码会执行吗?你会吃吗?肯定不会的。这就是过滤filer字面意思你也理解了。

       这个地方注意一下他是返回的BOOL值,但是如果我们需要是数据,我们就需要map一次进行数据整合(辣椒给你过滤,菜还要给你做,做好了好吧菜端出去)。

    RACSignal *signal = [[[[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        NSLog(@"接单了");
        NSLog(@"处理.....");
        [subscriber sendNext:@"炒菜完成,打电话给客户"];
        //完成
        [subscriber sendCompleted];
        return nil;
    }] filter:^BOOL(id value) {
        //过滤辣椒
        BOOL noLaJiao = YES;
        NSLog(@"过滤辣椒");
        return noLaJiao;
    }] map:^id(id value) {
        NSLog(@"菜加工中......");//-------------------------------------新增代码  这个地方开始加工菜了
        return value;
    }] doNext:^(id x) {
        NSLog(@"给点一次筷子什么的,米饭啊");
    }];
    
    //订单
    [signal subscribeNext:^(id x) {
        NSLog(@"客户处理开始吃了");
    }];

      上面就是对菜信号量的过滤组合处理,我们来看看运行的结果:

2015-02-02 17:08:37.816 TestRAC[5863:192403] 接单了
2015-02-02 17:08:37.816 TestRAC[5863:192403] 处理.....
2015-02-02 17:08:39.649 TestRAC[5863:192403] 过滤辣椒
2015-02-02 17:08:41.016 TestRAC[5863:192403] 菜加工中......
2015-02-02 17:08:42.017 TestRAC[5863:192403] 给点一次筷子什么的,米饭啊
2015-02-02 17:08:42.873 TestRAC[5863:192403] 客户处理开始吃了

     5.信号的依赖(就你你想的如果我要辣子鸡,没鸡啊)-- flattenMap处理信号依赖组合

       鸡肉对我们来说是数据,我们不需要关心细节,但是鸡对菜馆来说是一个信号,这个菜的依赖性太强,要等到把鸡买来才能做菜,不过这和你没关系。这个过程就是信号依赖。那如何用程序来做呢?

    @weakify(self);   //--------------------------------1
    RACSignal *signal = [[[[[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        NSLog(@"接单了");
        NSLog(@"处理.....");
        [subscriber sendNext:@"炒菜完成,打电话给客户"];
        //完成
        [subscriber sendCompleted];
        return nil;
    }] filter:^BOOL(id value) {
        //过滤辣椒
        BOOL noLaJiao = YES;
        NSLog(@"过滤辣椒");
        return noLaJiao;
    }] map:^id(id value) {
        NSLog(@"菜加工中......");
        return value;
    }] flattenMap:^RACStream *(id value) {  <span style="font-family: Arial, Helvetica, sans-serif;">//--------------------------------2</span>        NSLog(@"我才没鸡了,你怎么不早说....赶紧买鸡去.....");
        @strongify(self);//-------------------------------------------------------------3
        [self performSelector:@selector(buyChicken) withObject:nil afterDelay:3];//---------------------------4
        return [[[self rac_signalForSelector:@selector(buyChicken)] filter:^BOOL(id value) {
            BOOL goodChinken=YES;//-----------------------------5
            return goodChinken;
        }] map:^id(id value) {
            return @"买到好鸡了....是白羽鸡......";
        }];
    }] doNext:^(id x) {
        NSLog(@"给点一次筷子什么的,米饭啊");
    }];
    
    //订单
    [signal subscribeNext:^(id x) {
        NSLog(@"客户处理开始吃了-%@",x);
    }];

     //买鸡过程

- (void) buyChicken
{
    NSLog(@"买鸡回去.....买回来了......");
}

    运行结果:

2015-02-02 17:28:52.688 TestRAC[5968:201097] 接单了
2015-02-02 17:28:52.689 TestRAC[5968:201097] 处理.....
2015-02-02 17:28:54.777 TestRAC[5968:201097] 过滤辣椒
2015-02-02 17:28:55.992 TestRAC[5968:201097] 菜加工中......
2015-02-02 17:28:55.992 TestRAC[5968:201097] 我才没鸡了,你怎么不早说....赶紧买鸡去.....
2015-02-02 17:29:02.104 TestRAC[5968:201097] 买鸡回去.....买回来了......
2015-02-02 17:29:09.279 TestRAC[5968:201097] 给点一次筷子什么的,米饭啊
2015-02-02 17:29:10.911 TestRAC[5968:201097] 客户处理开始吃了-买到好鸡了....是白羽鸡......

     代码假如1-5,weakify和strongify是成对出现,self在block使用容易强饮用,所以这个不多讲,记住就行。格式就是block外一个weakify ,block内多个strongify。

     2行代码是对信号依赖的处理,关键字flattenMap。

     3模拟买鸡过程用时3秒钟。

     4这个是NSObject一个累扩展,这里先不讲,意思说产生一个信号,等信号触发后返回买鸡信号。如果你看不懂你可以这么做:

     5就不说了,看一下4改进的代码:

    //买鸡信号
    RACSignal *buyChinkenSignal = [[[self rac_signalForSelector:@selector(buyChicken)] filter:^BOOL(id value) {
        BOOL goodChinken=YES;
        return goodChinken;
    }] map:^id(id value) {
        return @"买到好鸡了....是白羽鸡......";
    }];
    
    @weakify(self);
    RACSignal *signal = [[[[[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        NSLog(@"接单了");
        NSLog(@"处理.....");
        [subscriber sendNext:@"炒菜完成,打电话给客户"];
        //完成
        [subscriber sendCompleted];
        return nil;
    }] filter:^BOOL(id value) {
        //过滤辣椒
        BOOL noLaJiao = YES;
        NSLog(@"过滤辣椒");
        return noLaJiao;
    }] map:^id(id value) {
        NSLog(@"菜加工中......");
        return value;
    }] flattenMap:^RACStream *(id value) {
        NSLog(@"我才没鸡了,你怎么不早说....赶紧买鸡去.....");
        @strongify(self);
        [self performSelector:@selector(buyChicken) withObject:nil afterDelay:3];
        
        //------------------------------------买鸡信号--------------------------------
        return buyChinkenSignal;
    }] doNext:^(id x) {
        NSLog(@"给点一次筷子什么的,米饭啊");
    }];

      大家估计理解有点困难,你可以把代码敲下来,运行看看。


     好长.....后面在总结........

     不同信号组合和控件的使用,信号数据Sequence等。





          

Reactive Cocoa 响应式编程开发实例讲解-中篇