首页 > 代码库 > 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 响应式编程开发实例讲解-中篇