ReactiveCocoa之从小白到老铁

作者: 麟young | 来源:发表于2017-06-28 18:48 被阅读1231次

    第〇:写在前面

    1.什么是ReactiveCocoa?

    Reactive: 响应式的、函数式的。
    Cocoa:苹果开发框架名称.
    Reactive + Cocoa = 具备函(Gao)数(Bi)式(Ge)编程思想的开发框架.

    2.啥是编程思想?

    常见的编程思想有:

    • 面向过程:打开冰箱->把大象放冰箱里->关上冰箱门。
    • 面向对象:对象的身高体重三围是属性;对象洗衣做饭看孩子是行为。佛曰:一切皆可幻化为对象,Everything is Object。
    • 链式编程:如果用过Monsary肯定见过 ->
      make.centerX.and.centerY.equalTo(self).with.offset(0.0);
      其实质是每个方法返回一个”返回值为自身且带有一个参数项“的Block,形成Block串联,也就是所谓的链式编程。
    • 函数式编程:
      思想上,函数式编程可谓博大精深,建议看这篇文章了解: 传送门
      形式上,函数是一等公民,常见函数(或闭包)作为参数或返回值传递、闭包嵌套、函数组合等形式。如:
    [array enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, BOOL *stop) {
        NSLog(@"%@",number);
    }];
    
    • 响应式编程:对于输入的变动加以监听,响应式地,每次产生对应的输出。

    ReactiveCocoa具备函数式编程和响应式编程思想,因此常被称之为函数响应式编程(Functional Reactive Programming)框架.

    3.为啥用ReactiveCocoa?

    因为它统一并简化了苹果原生框架大多场景,使得开发更为一致而且高效。
    ReactiveCocoa统一了iOS开发中常用的Target、Delegate、KVO、通知、NSTimer、网络异步回调等操作,并将相应的事件处理代码聚合在一处。

    4.CocoaPod引入

    由于ReactiveCocoa 3.0.0 版本及以下都是用纯Objective-C语言,这篇文章基于纯OC开发,因此Podfile文件长这样(建工程名字我这取的是RACDemo):

    use_frameworks!
    target 'RACDemo' do
        project 'RACDemo' 
        pod 'ReactiveCocoa', '~> 3.0.0’
    end
    

    第一:基础概念

    ReactiveCocoa的核心是携带信息的信号进行传递和订阅的过程。
    简要示意图如下:

    1.png

    接下来将会介绍与此相关的四个大的概念:

    1. 信号源
    2. 订阅者
    3. 回收站(RACDisposable)
    4. 调度器(上图中未提及)

    1.信号源

    • 信号的老祖宗 RACStream
      RACStream,顾名思义,就是指信号流。其继承结构如下图:
    2.png

    通常情况下不直接使用RACStream,而是用其子类进行信号传递。

    • 核心类RACSignal
      最常用的信号类,主要功能是创建信号、配置订阅者来订阅信号。
      信号在传递过程中包括正常传递、传递完成和传递失败的状态,分别对应下图中next、completed和error。
    3.png
    • 序列信号RACSequence
      指的是用来简化OC中的集合操作的信号类

    2.订阅者

    • 为了获取某个信号源中的值,通常需要对该信号源进行订阅,这就产生了订阅者的概念。
    • 在ReactiveCocoa中所有实现了RACSubscriber协议的类都可以作为信号源的订阅者。
      ReactiveCocoa源码中RACSubscriber协议规定了四个要实现的方法:
    @protocol RACSubscriber <NSObject>
    @required
    
    - (void)sendNext:(id)value;
    
    - (void)sendError:(NSError *)error;
    
    - (void)sendCompleted;
    
    - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
    
    @end
    
    

    其中: -sendNext:-sendError:-sendCompleted方法分别接收信号next、error和 completed事件;-didSubscribeWithDisposable:方法用来接收代表某次订阅的 RACDisposable(回收站)对象。

    3.回收站(RACDisposable)

    • 在ReactiveCocoa中并没有专门的类来代表一次完整的订阅行为,而间接地使用RACDisposable来表示订阅行为。
    • 订阅完成或失败时自动触发回收机制,因为它主要做一些资源回收和垃圾清理的工作。
    • 可以通过RACDisposable的dispose方法主动取消订阅行为,不再接受信号传递的信息。

    4.调度器(RACScheduler)

    RACScheduler是对GCD的简单封装,用来调度订阅任务。

    第二:核心信号之RACSignal

    创建、订阅、发送、回收一个信号

    - (void)testCreateSignal {
        // 1.创建信号
        RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            // 每当有订阅者订阅信号,就会调用此block。
            // 3.发送信号
            [subscriber sendNext:@"糖水"];
            [subscriber sendNext:@"盐水"];
            [subscriber sendNext:@"辣椒水"];
            // 如果不再发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
            // 5.订阅完成
            [subscriber sendCompleted];
            return [RACDisposable disposableWithBlock:^{
                // 7.当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
                // 执行完Block后,当前信号就不在被订阅了。
                NSLog(@"信号被销毁");
            }];
        }];
        // 2.订阅信号,依次获取步骤3中传递的信息
        // (此方法隐藏了两件事:一是创建订阅者RACSubscriber,二是为信号配置订阅者并触发订阅任务。其目的是简化信号订阅逻辑)
        [siganl subscribeNext:^(id x) {
            // 4.每当有信号sendNext: ,就会调用此block.
            NSLog(@"接收到数据:%@",x);
        } error:^(NSError *error) {
            NSLog(@"信号发送失败");
        } completed:^{
            // 6.第5步中[subscriber sendCompleted];执行后直接走此处方法
            NSLog(@"信号发送完成");
        }];
        // 8.继续订阅信号,依次获取步骤3中传递的信息
        [siganl subscribeNext:^(id x) {
            // 每当有信号sendNext: ,就会调用此block.
            NSLog(@"接收到数据:%@",x);
        }];
    }
    
       // 最终控制台输出结果为:
        2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一个订阅者接收到:糖水
        2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一个订阅者接收到:盐水
        2017-06-23 16:15:49.027 RACDemo[48262:2367413] 第一个订阅者接收到:辣椒水
        2017-06-23 16:15:49.028 RACDemo[48262:2367413] 信号发送完成
        2017-06-23 16:15:49.028 RACDemo[48262:2367413] 信号被销毁
        2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二个订阅者接收到:糖水
        2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二个订阅者接收到:盐水
        2017-06-23 16:15:49.028 RACDemo[48262:2367413] 第二个订阅者接收到:辣椒水
        2017-06-23 16:15:49.029 RACDemo[48262:2367413] 信号被销毁
    
    

    解读: 对于信号创建、订阅、发送、回收,我们可以把整个ReactiveCocoa框架形象地类比为水管管道机制:

    • 假装你拥有一个空水管的整体管道(也就是CocoaReactive框架)
    • 信号(RACSignal)的创建犹如开启一个入水阀阀门
    • 信号携带的信息……举个栗子:糖水、盐水、辣椒水
    • 创建信号(createSignal:),如同入水阀中陆续倒上这几种水,搁着,等待流动(传递)(sendNext:)。
    • 订阅者(RACSubscriber)相当于一个出水阀,进行订阅(subscribeNext:)就是拧开出水阀,和被订阅的入水阀(RACSignal)连通,这样水就开始流动,进行传递(sendNext:)。
    • 一旦水流抵达出水阀后不再继续管道流通(sendCompleted:),或者水在半路堵死了(sendError:),这时候没必要继续保持出水阀和入水阀间的连通(或者说订阅)状态(节约用水,人人有责),会自动关闭出水阀阀门并阻断连通,也就是触发回收([RACDisposable disposableWithBlock:^{})行为解除订阅,此时订阅过程结束。
    • 回收行为的代码往往在信号创建时一并书写;回收(RACDisposable)代表着一次订阅过程的结束。
    • 每个出水阀的订阅(也就是每次拧开一个出水阀的阀门),都会立即接收到入水阀先后放进去的信息(糖水、盐水、辣椒水)。
    • 如果有多个出水阀(订阅者)订阅一个入水阀的情况,则第一个出水阀被回收(拧上)后,再打开下一个出水阀订阅,结束后再被回收,再拧开下一个出水阀……一次拧开一个、依开启顺序进行。

    画个图来表示上面的代码吧:

    4.png

    第三:华丽的信号家族之RACSubject

    • 上面的图片描述了一般信号传递的机制,华丽版的信号传递无非是在此基础上,修修水管管道,调整水阀间的连通关系等。
    • 由于水阀水管实用性强,生活家居必备,嵌入在现实生活可以连通城市地下网络,嵌入在苹果Cocoa框架中,更是将原来不灵动的框架硬生生灵动起来,尤其是上文已经提到的Target、Delegate、KVO、通知、NSTimer、网络异步回调等,被信号化后结构便一致起来。

    下面说说更多有趣的信号场景

    1. RACSubject

    RACSubject和RACSignal的最大不同是,RACSubject既是信号的子类,同时实现了订阅者RACSubscriber协议,因此也可以充当订阅者发送信息,于是乎,看个栗子:

    - (void)testRACSubject {
        // RACSubject使用步骤
        // 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
        // 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
        // 3.发送信号 sendNext:(id)value
        
        // RACSubject:底层实现和RACSignal不一样。
        // 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
        // 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
        
        // 1.创建信号
        RACSubject *subject = [RACSubject subject];
        
        // 2.订阅信号
        [subject subscribeNext:^(id x) {
            // block调用时刻:当信号发出新值,就会调用.
            NSLog(@"第一个订阅者%@",x);
        }];
        [subject subscribeNext:^(id x) {
            // block调用时刻:当信号发出新值,就会调用.
            NSLog(@"第二个订阅者%@",x);
        }];
        
        // 3.发送信号
        [subject sendNext:@"糖水"];
        [subject sendNext:@"盐水"];
        [subject sendNext:@"辣椒水"];
    }
    
        // 最终控制台输出结果为:
        2017-06-23 16:12:13.565 RACDemo[48194:2363636] 第一个订阅者接收到:糖水
        2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二个订阅者接收到:糖水
        2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第一个订阅者接收到:盐水
        2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二个订阅者接收到:盐水
        2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第一个订阅者接收到:辣椒水
        2017-06-23 16:12:13.566 RACDemo[48194:2363636] 第二个订阅者接收到:辣椒水
        
    

    解读:

    • 首先写法上,sendNext:不用挤在信号创建的block中去写了,因为RACSubject对象本身可以作为订阅者调用此方法。
    • 其次,RACSubject的订阅方法(subscribeNext:)仅仅是拧开出水阀,此时进水阀中还没倒进水。
    • 当主动倒水(sendNext:)时,会把本次放进去的水传递给所有的出水阀
    • RACSubject和RACSignal最大的不同在于放水(sendNext:)的时机,前者是配置好出水口后主动流向所有出水口,后者是出水口打开后将入水阀门中的水引出。

    画个图吧:

    5.png

    2. RACReplaySubject

    不要被更长的名字吓到,其实它更简单。如果说RACSubject是对出水管都统一配置好再送水的过程,那么RACReplaySubject相当于把水流都配置好等待被出水管引出的过程。简单的代码走起:

    - (void)testRACReplaySubject {
        // RACReplaySubject:底层实现和RACSubject不一样。
        // 1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
        // 2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock
        
        // 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。
        // 也就是先保存值,在订阅值。
        
        // 1.创建信号
        RACReplaySubject *replaySubject = [RACReplaySubject subject];
        
        // 2.发送信号
        [replaySubject sendNext:@"糖水"];
        [replaySubject sendNext:@"盐水"];
        [replaySubject sendNext:@"辣椒水"];
        
        // 3.订阅信号
        [replaySubject subscribeNext:^(id x) {
            NSLog(@"第一个订阅者接收到:%@",x);
        }];
        
        // 订阅信号
        [replaySubject subscribeNext:^(id x) {
            NSLog(@"第一个订阅者接收到:%@",x);
        }];
    }
    
        // 最终控制台输出结果为:
        2017-06-26 17:52:39.670 RACDemo[61435:3788527] 第一个订阅者接收到:糖水
        2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第一个订阅者接收到:盐水
        2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第一个订阅者接收到:辣椒水
        2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第二个订阅者接收到:糖水
        2017-06-26 17:52:39.671 RACDemo[61435:3788527] 第二个订阅者接收到:盐水
        2017-06-26 17:52:39.672 RACDemo[61435:3788527] 第二个订阅者接收到:辣椒水
    
    

    解读:

    • 输出的结果和RACSignal完全一致,只是写法上略有不同
    • 这意味着,继承自RACSubject的RACReplaySubject实现了基本信号的功能
    • 但是,RACReplaySubject还可以在上面的订阅代码后面,继续sendNext:,实现和RACSubject一样的功能.(有兴趣的童鞋可以在上面代码最后加一句[replaySubject sendNext:@"别的水"];试一下,看看打印的结果)

    对于上面的代码,依旧画个图表示吧:

    6.png

    总结: RACSubject实现每个水流群发到所有出水口,RACReplaySubject实现每个出水口接收所有水流。
    扩展: 有人会把RACSignal和RACSubject中控制水流方式的不同,分为冷信号热信号这么一说。

    • 冷,即为高冷,你不订阅(打开出水口)就没办法收到信号(水流)信息,这说的就是RACSignal
    • 热,即为热情,管你有木有订阅,先送水再说,大不了没人收到我的信号信息,这说的就是RACSubject。
    • 冷信号能否变成热信号,热情起来呢?可以。接下来说说RACMulticastConnection~

    3. RACMulticastConnection

    常规RACSignal被订阅多次时,每次都要将创建信号的Block中所有代码(包括sendNext:)方法执行一遍,可否向RACSubject一样,只sendNext:一次就发送给所有订阅者,也就是所谓的冷信号转变为热信号?看代码:

    - (void)testRACMulticastConnection {
        // RACMulticastConnection使用步骤:
        // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
        // 2.创建连接 RACMulticastConnection *connect = [signal publish];
        // 3.订阅信号,注意:订阅的不在是之前的信号,而是连接的信号。 [connect.signal subscribeNext:nextBlock]
        // 4.连接 [connect connect]
        
        // RACMulticastConnection底层原理:
        // 1.创建connect,connect.sourceSignal -> RACSignal(原始信号)  connect.signal -> RACSubject
        // 2.订阅connect.signal,会调用RACSubject的subscribeNext,创建订阅者,而且把订阅者保存起来,不会执行block。
        // 3.[connect connect]内部会订阅RACSignal(原始信号),并且订阅者是RACSubject
        // 3.1.订阅原始信号,就会调用原始信号中的didSubscribe
        // 3.2 didSubscribe,拿到订阅者调用sendNext,其实是调用RACSubject的sendNext
        // 4.RACSubject的sendNext,会遍历RACSubject所有订阅者发送信号。
        // 4.1 因为刚刚第二步,都是在订阅RACSubject,因此会拿到第二步所有的订阅者,调用他们的nextBlock
        
        
        // 需求:假设在一个信号中发送请求,每次订阅一次都会发送请求,这样就会导致多次请求。
        // 解决:使用RACMulticastConnection就能解决.
        
        // RACMulticastConnection:解决重复请求问题
        // 1.创建信号
        RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"发送请求");
            [subscriber sendNext:@"糖水"];
            [subscriber sendNext:@"盐水"];
            [subscriber sendNext:@"辣椒水"];
            return nil;
        }];
        // 2.创建连接
        RACMulticastConnection *connect = [signal publish];
        // 3.订阅信号,
        // 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
        [connect.signal subscribeNext:^(id x) {
            NSLog(@"第一个订阅者接收到:%@",x);
        }];
        [connect.signal subscribeNext:^(id x) {
            NSLog(@"第二个订阅者接收到:%@",x);
        }];
        // 4.连接,激活信号
        [connect connect];
    }
    
        // 最终控制台输出结果为:
        2017-06-26 18:44:55.057 RACDemo[61893:3825826] 发送请求
        2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一个订阅者接收到:糖水
        2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第二个订阅者接收到:糖水
        2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一个订阅者接收到:盐水
        2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第二个订阅者接收到:盐水
        2017-06-26 18:44:55.058 RACDemo[61893:3825826] 第一个订阅者接收到:辣椒水
        2017-06-26 18:44:55.059 RACDemo[61893:3825826] 第二个订阅者接收到:辣椒水
    
    

    解读:

    • RACMulticastConnection的庐山真面目依旧走RACSubject订阅机制
    • 恩,不画示意图了,同RACSubject~

    第四:轻松点,聊聊那些被封装好的简单信号及使用

    如果,我说如果,理解ReactiveCocoa的原理很为难,那么使用它却极其简单……

    1. 监听是否实现了某方法 之 rac_signalForSelector
      应用场景:判断某个代理是否实现某方法。
      或者,下面栗子中监控某Button所在的VC中是否实现了点击方法
    if ([self rac_signalForSelector:@selector(clickTestButton:)]) {
        NSLog(@"点击了测试按钮");
    }
    
    1. 替代KVO的rac_valuesAndChangesForKeyPath
      有了它,我已忘记原来的KVO长什么样子
    [[self rac_valuesAndChangesForKeyPath:@"testNum"    
     options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
        NSLog(@"new value info is %@", x);
    }];
    
    
    1. 监听事件rac_signalForControlEvents,如点击事件。
      话说,你还在为按钮addTarget:action:forControlEvents:然后找个别的地儿写实现方法吗?
    [[self.testButton rac_signalForControlEvents:UIControlEventTouchUpInside]    
     subscribeNext:^(id x) {
        NSLog(@"已经实现点击事件");
    }];
    
    1. 监听通知rac_addObserverForName,如键盘<s>叹气</s>弹起的通知
    [[[NSNotificationCenter defaultCenter]    
     rac_addObserverForName:UIKeyboardWillShowNotification object:nil]    
              subscribeNext:^(id x) {
        NSLog(@"键盘弹出");
    }];
    
    1. 手势信号 之 rac_gestureSignal
    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] init];
    [panGesture.rac_gestureSignal subscribeNext:^(id x) {
        NSLog(@"拖动手势:%@", x);
    }];
    [self.view addGestureRecognizer:panGesture];
    
    1. UITextView或UITextField文字变化信号 之 rac_textSignal
    [_textField.rac_textSignal subscribeNext:^(id x) {
        NSLog(@"当前文字:%@", x);
    }];
    

    第四:升级篇 知识进阶

    如果你觉着上面的知识足矣用来开发项目,理论上可以简化代码量,但应对更复杂的逻辑还是too young too naive,因此,接下来会聊到:

    • RACSequence
    • RACCommand
    • RACSchedule
    • RACChannel
    • 信号转换
    • 信号拼接
    • RAC常用宏

    1. RACSequence

    为了简化OC语言中的集合类操作,发明了RACSequence。

    原理:见ReactiveCocoa v2.5 源码解析之架构总览 中的RACSequence部分。

    实现,举个栗子:

    - (void)testRACSequence {
        // 1.遍历数组
        NSArray *numbers = @[@1, @2, @3, @4];
        // 这里其实是三步
        // 第一步: 把数组转换成集合RACSequence,即numbers.rac_sequence
        // 第二步: 把集合RACSequence转换RACSignal信号类,numbers.rac_sequence.signal
        // 第三步: 订阅信号,激活信号,会自动把集合中的所有值,遍历出来。
        [numbers.rac_sequence.signal subscribeNext:^(id x) {
           NSLog(@"%@",x);
        }];
        
        // 2.遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
        NSDictionary *dict = @{@"name":@"ABC",@"age":@22};
        // 下面RACTuple元组的概念请自行查阅
        [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
            // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
            RACTupleUnpack(NSString *key,NSString *value) = x;
            // 相当于以下写法
            // NSString *key = x[0];
            // NSString *value = x[1];
            NSLog(@"%@ %@", key, value);
    }];
    

    2. RACCommand

    RACCommand是一个可以包裹一个信号,并对信号的执行、完成、失败状态进行把控的类。通常把异步网络请求封装成信号的形式,然后通过RACCommand包裹信号,实时反馈网络请求状态(请求中、请求成功、请求失败)。
    模拟网络请求,举个栗子:

    /// 先在当前VC中添加属性:
    @interface ViewController ()
    @property (nonatomic, strong) RACCommand *command;
    @end
    
    /// 实现文件里写入此示例代码
    - (void)testRACCommand {
        if (!_command) {
            _command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
                return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                    NSLog(@"开始请求网络");
                    // 假装请求到数据
                    [subscriber sendNext:@"请求到的数据"];
                    // 请求完成
                    [subscriber sendCompleted];
                    return [RACDisposable disposableWithBlock:^{
                        NSLog(@"订阅过程完成");
                    }];
                }];
            }];
            
            // 监控是否执行中状态
            [_command.executing subscribeNext:^(id x) {
                NSNumber *executing = (NSNumber *)x;
                NSString *stateStr = executing.boolValue? @"请求状态" : @"未请求状态";
                NSLog(@"%@", stateStr);
            }];
            
            // 监控请求成功的状态
            [_command.executionSignals.switchToLatest subscribeNext:^(id x) {
                NSLog(@"请求成功后,拿到数据: %@", x);
            }];
            
            // 监控请求失败的状态
            [_command.errors subscribeNext:^(id x) {
                NSLog(@"请求失败: %@", x);
            }];
        }
        
        // 执行操作 调用上面createSignal中的block操作
        [_command execute:nil];
    }
    
        打印结果为:
        2017-06-27 17:10:21.672 RACDemo[65799:6318742] 未请求状态
        2017-06-27 17:10:21.680 RACDemo[65799:6318742] 请求状态
        2017-06-27 17:10:21.681 RACDemo[65799:6318742] 开始请求网络
        2017-06-27 17:10:21.681 RACDemo[65799:6318742] 请求成功后,拿到数据: 请求到的数据
        2017-06-27 17:10:21.682 RACDemo[65799:6318742] 订阅过程完成
        2017-06-27 17:10:21.682 RACDemo[65799:6318742] 未请求状态
    

    解读:

    • 这里RACCommand的创建使用initWithSignalBlock:方法,其参数是一个“带有一个参数并返回信号”的block,因此直接在后面返回一个信号即可。信号的创建方式和前文一致。
    • 创建完RACCommand后,就可以用RACCommand的相关属性看到当前Signal的状态。
    • 不同于使用信号订阅的三种状态"subscribeNext:"、"subscribeError:"和"subscribeCompleted",这里用RACCommand的"executionSignals"属性代表成功拿到发送的信号,"errors"属性表示失败的信号。
    • 在执行[_command execute:nil];前,RACCommand的"executing"属性默认是NO,一旦执行,"executing"属性就会变成YES状态,直到出现"sendCompleted"或"sendError:"情况时,"executing"属性再次变成NO
    • 由于RACCommand对信号执行状态多了更细致的描述,因此往往适用于异步的操作,如网络请求这种需要在请求状态时展示HUD、请求成功时隐藏HUD、请求成功的数据用来刷新视图的场景,统统可以写到一块一并被处理。
    • 这里的代码只是模拟了网络请求,真正开发时需要封装现有的网络请求框架,在请求到数据、请求失败和请求成功时加入"sendNext:"、"sendError:"和"sendCompleted"代码。

    扩展:
    这篇文章详细介绍了RACCommand原理和能干的事儿,可以在了解完本篇后面的知识后阅读学习。
    ReactiveCocoa基本组件:理解和使用RACCommand

    3. RACScheduler

    RACScheduler是对GCD的一层封装,管理信号的线程任务,其原理建议参考这篇文章:
    ReactiveCocoa 中 RACScheduler是如何封装GCD的,注意其中对deliverOn方法的描述。
    RACScheduler用法相对简单,后面介绍信号拼接时会代码提及。

    4. RACChannel

    原理篇请移步:RAC 中的双向数据绑定 RACChannel
    平时,主要用RACChannel的子类RACChannelTerminal来实现UI和属性的双向绑定功能,即UI变化属性跟随变化,属性变化UI也跟随变化。举个栗子:

    
    @interface ViewController ()
    
    @property (weak, nonatomic) IBOutlet UITextField *textField; // 输入框
    @property (nonatomic, copy) NSString *stringText;            // 输入框显示的文字
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        RACChannelTerminal *textFieldChannelT = self.textField.rac_newTextChannel;
        // 输入框文本变化反应到stringText属性上
        RAC(self, stringText) = textFieldChannelT;  
        // stringText属性对应的文字订阅到输入框上  这样就实现了双向绑定
        [RACObserve(self, stringText) subscribe:textFieldChannelT]; 
    }
    
    

    扩展:

    系统框架还提供了以下可以返回RACChannelTerminal的方法:

    • NSUserDefaults 之 rac_channelTerminalForKey:
    • UIDatePicker 之 rac_newDateChannelWithNilValue:
    • UISegmentedControl 之 rac_newSelectedSegmentIndexChannelWithNilValue:
    • UISlider 之 rac_newValueChannelWithNilValue:

    5. RAC常用宏

    • RAC(TARGET, [KEYPATH, [NIL_VALUE]]) 常用于给某个对象的某个属性绑定信号值
    举个栗子:
    // 每当textField文本变化时,就会把文本信息传递给self的textFieldText属性
    RAC(self, textFieldText) = [self.textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
    
    
    • RACObserve(TARGET, KEYPATH) 监听某个对象的某个属性,一旦属性值发生变化,立马激活信号。
    如前面KVO的例子:
    [[self rac_valuesAndChangesForKeyPath:@"testNum"    
     options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
        NSLog(@"new value info is %@", x);
    }];
    
    使用此宏可以改写为:
    [RACObserve(self, testNum) subscribeNext:^(id x) {
        NSLog(@"new value is %@", x);
    }];
    
    
    • @weakify(obj)@strongify(obj) 为避免Block循环引用而生的一对宏
    通常,blobk引用其外部变量,为避免持有外部对象,通常使用这一对宏来避免,如:
    
    @weakify(self);
    [RACObserve(self, testNum) subscribeNext:^(id x) {
        @strongify(self);
        NSLog(@"new value is %@", self.x);
    }];
    
    
    • RACTurplePackRACTupleUnpack分别代表打包生成元组 和 解包分解元组。
    // RACTuplePack生成元组
    RACTuple *tuple = RACTuplePack(@"数据1", @2);
    // RACTupleUnpack分解元组 参数:被解析的变量名
    RACTupleUnpack(NSString *str, NSNumber *num) = tuple;
    NSLog(@"%@ %@", str, num);
    
    
    • RACChannelTo 双向绑定终端
    一般情况下,使用RACChannelTo(self, name) = RACChannelTo(self.model, name);即可完成双向等值绑定功能,即一方变化另一方也跟随变化,但前提是触发属性的KVO机制才能实现。
    
    
    二般情况下,举个栗子:
    
    //首先在VC中添加两个属性:
    @property (nonatomic, copy) NSString *valueA;
    @property (nonatomic, copy) NSString *valueB;
    
    // 然后添加测试方法
    - (void)testRACChannelTo:(id)sender {
        RACChannelTerminal *channelA = RACChannelTo(self, valueA);
        RACChannelTerminal *channelB = RACChannelTo(self, valueB);
        [[channelA map:^id(NSString *value) {
            if ([value isEqualToString:@"西"]) {
                return @"东";
            }
            return value;
        }] subscribe:channelB];
        [[channelB map:^id(NSString *value) {
            if ([value isEqualToString:@"左"]) {
                return @"右";
            }
            return value;
        }] subscribe:channelA];
        [[RACObserve(self, valueA) filter:^BOOL(id value) {
            return value ? YES : NO;
        }] subscribeNext:^(NSString* x) {
            NSLog(@"你向%@", x);
        }];
        [[RACObserve(self, valueB) filter:^BOOL(id value) {
            return value ? YES : NO;
        }] subscribeNext:^(NSString* x) {
            NSLog(@"他向%@", x);
        }];
        self.valueA = @"西";
        self.valueB = @"左";
    }
    
    输出结果为:
    2017-06-30 17:19:50.125 RACDemo[78483:7464849] 你向西
    2017-06-30 17:19:50.126 RACDemo[78483:7464849] 他向东
    2017-06-30 17:19:50.126 RACDemo[78483:7464849] 他向左
    2017-06-30 17:19:50.127 RACDemo[78483:7464849] 你向右
    
    

    6. 信号转换

    • map 转换信号值,返回新信号
    - (void)testMap {
        RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"石"];
            return nil;
        }] map:^id(NSString* value) {
            if ([value isEqualToString:@"石"]) {
                return @"金";
            }
            return value;
        }];
        [signal subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
    }
    
    输出结果为:
    2017-06-29 10:35:41.914 RACDemo[69180:6961894] 金
    
    
    • filter 过滤
    - (void)testFilter {
        [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@(15)];
            [subscriber sendNext:@(17)];
            [subscriber sendNext:@(21)];
            [subscriber sendNext:@(14)];
            [subscriber sendNext:@(30)];
            return nil;
        }] filter:^BOOL(NSNumber* value) {
            return value.integerValue >= 18;
        }] subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
    }
    
    输出结果为:
    2017-06-29 10:39:13.718 RACDemo[69242:6964994] 21
    2017-06-29 10:39:13.718 RACDemo[69242:6964994] 30
    
    
    • flattenMap 直接转成新的信号
    - (void)testFlattenMap {
        [[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"打蛋液");
            [subscriber sendNext:@"蛋液"];
            [subscriber sendCompleted];
            return nil;
        }] flattenMap:^RACStream *(NSString* value) {
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                NSLog(@"把%@倒进锅里面煎",value);
                [subscriber sendNext:@"煎蛋"];
                [subscriber sendCompleted];
                return nil;
            }];
        }] flattenMap:^RACStream *(NSString* value) {
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                NSLog(@"把%@装到盘里", value);
                [subscriber sendNext:@"上菜"];
                [subscriber sendCompleted];
                return nil;
            }];
        }] subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
    }
    
    输出结果为:
    2017-06-29 11:02:05.035 RACDemo[69369:6975870] 打蛋液
    2017-06-29 11:02:05.036 RACDemo[69369:6975870] 把蛋液倒进锅里面煎
    2017-06-29 11:02:05.036 RACDemo[69369:6975870] 把煎蛋装到盘里
    2017-06-29 11:02:05.036 RACDemo[69369:6975870] 上菜
    
    
    • take 只取前几个订阅
    - (void)testTake {
        RACSubject *subject = [RACSubject subject];
        //只取前两次的信号值
        [[subject take:2] subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
        
        [subject sendNext:@"1"];
        [subject sendNext:@"2"];
        [subject sendNext:@"3"];
        [subject sendNext:@"4"];
    }
    
    输出结果为:
    2017-06-29 11:41:22.418 RACDemo[69701:7001833] 1
    2017-06-29 11:41:22.418 RACDemo[69701:7001833] 2
    
    
    • takeLast 只取最后几个订阅
    - (void)testTakeLast {
        RACSubject *subject = [RACSubject subject];
        //只取前两次的信号值
        [[subject takeLast:2] subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
        
        [subject sendNext:@"1"];
        [subject sendNext:@"2"];
        [subject sendNext:@"3"];
        [subject sendNext:@"4"];
        // 注意此处必须订阅完成 才能使用takeLast进行最后几次订阅的统计
        [subject sendCompleted];
    }
    
    输出结果为:
    2017-06-29 11:48:15.642 RACDemo[69834:7008684] 3
    2017-06-29 11:48:15.642 RACDemo[69834:7008684] 4
    
    
    • skip 先跳过几个订阅
    // 默认UITextField创建时就能订阅到其信号,往往会跳过第一次信号
    [[self.textfield.rac_textSignal skip:1] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    • ignore 内部调用filter过滤,忽略掉ignore的值
    - (void)testIgnore {
        RACSubject *subject = [RACSubject subject];
        //只取前两次的信号值
        [[subject ignore:@"2"] subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
        
        [subject sendNext:@"1"];
        [subject sendNext:@"2"];
        [subject sendNext:@"3"];
        [subject sendNext:@"4"];
    }
    
    输出结果为:
    2017-06-29 12:06:56.999 RACDemo[69907:7018889] 1
    2017-06-29 12:06:57.000 RACDemo[69907:7018889] 3
    2017-06-29 12:06:57.000 RACDemo[69907:7018889] 4 
    
    
    • distinctUntilChanged 只订阅当前信号值不同的信号
    - (void)testDistinctUntilChanged {
        RACSubject *subject = [RACSubject subject];
        //当上一次的值和这次的值有明显变化的时候就会发出信号,否则会忽略掉
        //一般用来刷新UI界面,当数据有变化的时候才会刷新
        [[subject distinctUntilChanged] subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
        
        [subject sendNext:@"A"];
        [subject sendNext:@"A"];
        [subject sendNext:@"B"];
    }
    
    输出结果为:
    2017-06-29 15:39:03.417 RACDemo[70120:7083874] A
    2017-06-29 15:39:03.418 RACDemo[70120:7083874] B
    
    
    • switchTolatest 获取信号组中的信号
    如上面RACCommand的代码栗子中:
    // 监控请求成功的状态
    [_command.executionSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"请求成功后,拿到数据: %@", x);
    }];
    
    这里的executionSignals返回的是信号组(本身也是信号类型), RACCommand每`excute:`一次就会向信号组中扔进去新的信号,这里我们先拿到信号组,然后用switchToLatest属性拿到这个最新的信号,最后再进行订阅。
    
    

    7. 信号拼接

    • concat 拼接。注意:在signalA后拼接signalB,只有signalA发送完成sendCompleted,signalB才会被拼接。
    - (void)testConcat {
        RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"我恋爱啦"];
            [subscriber sendCompleted];
            return nil;
        }];
        RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"我结婚啦"];
            [subscriber sendCompleted];
            return nil;
        }];
        [[signalA concat:signalB] subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
    }
    
    输出结果为:
    2017-06-30 10:47:36.654 RACDemo[75454:7236811] 我恋爱啦
    2017-06-30 10:47:36.655 RACDemo[75454:7236811] 我结婚啦
    
    
    • then 转接。连接下一个信号,当前信号完成后,才连接then后的信号。
    - (void)testThen {
        // then:用于连接两个信号,当第一个信号完成,才会连接then返回的信号
        // 注意使用then,之前信号的值会被忽略掉.
        // 底层实现:1、先过滤掉之前的信号发出的值。2.使用concat连接then返回的信号
        [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@1];
            // 这里必须sendCompleted才能执行then后的信号
            [subscriber sendCompleted];
            return nil;
        }] then:^RACSignal *{
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                [subscriber sendNext:@2];
                return nil;
            }];
        }] subscribeNext:^(id x) {
            // 只能接收到第二个信号的值,也就是then返回信号的值
            NSLog(@"%@",x);
        }];
    }
    
    输出结果为:
    2017-06-30 10:44:08.578 RACDemo[75394:7233518] 2
    
    
    • merge 合并。任何一个信号有新值的时候就会被订阅。
    - (void)testMerge {
        RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"纸厂污水"];
            return nil;
        }];
        RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"电镀厂污水"];
            return nil;
        }];
        [[RACSignal merge:@[signalA, signalB]] subscribeNext:^(id x) {
            NSLog(@"处理%@",x);
        }];
    }
    
    输出结果为:
    2017-06-30 10:55:58.645 RACDemo[75697:7244204] 处理纸厂污水
    2017-06-30 10:55:58.645 RACDemo[75697:7244204] 处理电镀厂污水
    
    
    • zip(同步合并压缩) + reduce(分解元组)
    - (void)testZip {
        RACSubject *letters = [RACSubject subject];
        RACSubject *numbers = [RACSubject subject];
        
        RACSignal *zipSignal = [RACSignal zip:@[letters,numbers] reduce:^id(NSString *letter,NSString *number){
            return [letter stringByAppendingString:number];
        }];
        [zipSignal subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
        
        [letters sendNext:@"A"];
        [letters sendNext:@"B"];
        [numbers sendNext:@"1"];
        [numbers sendNext:@"2"];
        [numbers sendNext:@"3"];
        [letters sendNext:@"C"];
        [letters sendNext:@"D"];
        [numbers sendNext:@"4"];
    }
    
    输出结果为:
    2017-06-30 11:03:50.907 RACDemo[75837:7251093] A1
    2017-06-30 11:03:50.908 RACDemo[75837:7251093] B2
    2017-06-30 11:03:50.908 RACDemo[75837:7251093] C3
    2017-06-30 11:03:50.908 RACDemo[75837:7251093] D4
    
    /// zip后发出的信号值是元组类型,因此这里使用 reduce: 方法直接分解元素,拿到元组中对应的值
    /// zip的合并行为是按顺序取出各个信号然后合并发出的
    /// 也就是说,letters的第一个值A和number的第一个值1合并输出A1,第二个值B和number的第二个值2合并输出B2       
    /// 假设D后面,还有E,F,G...,但是没有对应的number信号,zip的合并行为就无法进行下去了.
    
    
    • combineLatest(最新值合并压缩) + reduce(分解元组)
    - (void)testCombineLatest{
        RACSubject *letters = [RACSubject subject];
        RACSubject *numbers = [RACSubject subject];
        
        RACSignal *combined = [RACSignal combineLatest:@[letters,numbers] reduce:^id(NSString *letter,NSString *number){
            return [letter stringByAppendingString:number];
        }];
        [combined subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
        
        [letters sendNext:@"A"];
        [letters sendNext:@"B"];
        [numbers sendNext:@"1"];
        [numbers sendNext:@"2"];
        [letters sendNext:@"C"];
        [numbers sendNext:@"3"];
        [letters sendNext:@"D"];
        [numbers sendNext:@"4"];
    }
    
    输出结果为:
    2017-06-30 11:12:59.470 RACDemo[75910:7256870] B1
    2017-06-30 11:12:59.471 RACDemo[75910:7256870] B2
    2017-06-30 11:12:59.471 RACDemo[75910:7256870] C2
    2017-06-30 11:12:59.471 RACDemo[75910:7256870] C3
    2017-06-30 11:12:59.471 RACDemo[75910:7256870] D3
    2017-06-30 11:12:59.472 RACDemo[75910:7256870] D4
    
    /// combineLatest后发出的信号值是元组类型,因此这里使用 reduce: 方法直接分解元素,拿到元组中对应的值
    /// 上面代码可以发现:letter信号的A值被更新的B值覆盖了,所以接下来接收到number信号的1时候,合并,输出信号B1.
    /// 当接收到C的时候,与number的最新的值2合并,输出信号C2.
    
    
    • takeUntil 获取信号直到某个信号开始执行
    - (void)testTakeUntil {
        [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
                [subscriber sendNext:@"直到世界的尽头才能把我们分开"];
            }];
            return nil;
        }] takeUntil:[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"世界的尽头到了");
                [subscriber sendNext:@"世界的尽头到了"];
            });
            return nil;
        }]] subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
    }
    
    输出结果为:
    2017-06-30 11:46:55.103 RACDemo[75983:7272054] 直到世界的尽头才能把我们分开
    2017-06-30 11:46:56.103 RACDemo[75983:7272054] 直到世界的尽头才能把我们分开
    2017-06-30 11:46:57.103 RACDemo[75983:7272054] 直到世界的尽头才能把我们分开
    2017-06-30 11:46:58.103 RACDemo[75983:7272054] 直到世界的尽头才能把我们分开
    2017-06-30 11:46:59.102 RACDemo[75983:7272054] 世界的尽头到了
    
    
    • doNext、doCompleted、doError 分别在sendNext、sendConpleted和sendError前执行操作
    - (void)doNextOrComleted {
        [[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@1];
            [subscriber sendCompleted];
            return nil;
        }] doNext:^(id x) {
            // 执行[subscriber sendNext:@1];之前会调用这个Block
            NSLog(@"doNext");;
        }] doCompleted:^{
            // 执行[subscriber sendCompleted];之前会调用这个Block
            NSLog(@"doCompleted");;
        }] subscribeNext:^(id x) {
            NSLog(@"%@",x);
        }];
    }
    
    输出结果为:
    2017-06-30 11:54:39.750 RACDemo[76163:7277651] doNext
    2017-06-30 11:54:39.751 RACDemo[76163:7277651] 1
    2017-06-30 11:54:39.751 RACDemo[76163:7277651] doCompleted
    
    
    • rac_liftSelector 每个信号至少sendNext一次后,拿到每个信号最新的信号值,然后执行selector中的操作
    - (void)testLiftSelector {
        // 当多次数据请求时,需要全部请求结束之后才会进行下一步UI刷新等,可以用这个rac_liftSelector
        // 第一部分数据
        RACSignal *section01Signal01 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"section01数据请求");
            [subscriber sendNext:@"section01请求到的数据"];
            return nil;
        }];
        // 第二部分数据
        RACSignal *section01Signal02 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"section02数据请求");
            [subscriber sendNext:@"section02请求到的数据"];
            return nil;
        }];
        // 第三部分数据
        RACSignal *section01Signal03 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"section03数据请求");
            [subscriber sendNext:@"section03请求到的数据"];
            return nil;
        }];
        // 可以在所有信号请求完成之后再执行方法,只需把三个结果传出去即可
        // 注意下面@selector中的参数数量和传递和信号数量是一致的
        [self rac_liftSelector:@selector(refreshUI:::) withSignals:section01Signal01,section01Signal02,section01Signal03, nil];
    }
    
    - (void)refreshUI:(NSString *)str1 :(NSString *)str2 :(NSString *)str3 {
        NSLog(@"最终数据为:\n%@\n%@\n%@", str1, str2, str3);
    }
    
    输出结果为:
    2017-06-30 12:07:43.382 RACDemo[76282:7286396] section01数据请求
    2017-06-30 12:07:43.382 RACDemo[76282:7286396] section02数据请求
    2017-06-30 12:07:43.382 RACDemo[76282:7286396] section03数据请求
    2017-06-30 12:07:43.383 RACDemo[76282:7286396] 最终数据为:
    section01请求到的数据
    section02请求到的数据
    section03请求到的数据
    
    

    8. 信号时间或频率控制

    • timeOut 超时。超时一定时间后自动sendError
    - (void)testTimeOut {
        RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"100"];
            return nil;
        }] timeout:1 onScheduler: [RACScheduler currentScheduler]]; // 这里将时间操作放在当前线程中执行
        
        [signal subscribeNext:^(id x) {
            NSLog(@"%@",x);
        } error:^(NSError *error) {
            NSLog(@"1秒后会自动调用");
        }];
    }
    
    输出结果为:
    2017-06-30 12:20:41.715 RACDemo[76418:7295742] 100
    2017-06-30 12:20:42.812 RACDemo[76418:7295742] 1秒后会自动调用
    
    
    • delay 延迟。延迟一定时间后再进行信息传递
    - (void)testDelay {
        [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSLog(@"等等我,我还有3秒钟就到了");
            [subscriber sendNext:nil];
            [subscriber sendCompleted];
            return nil;
        }] delay:3] subscribeNext:^(id x) {
            NSLog(@"我到了");
        }];
    }
    
    输出结果为:
    2017-06-30 12:27:15.584 RACDemo[76593:7301745] 等等我,我还有3秒钟就到了
    2017-06-30 12:27:18.884 RACDemo[76593:7301745] 我到了
    
    
    • replay 同RACReplaySubject实现原理一样,将要传递的值打包保存一次,然后依次派送给订阅者。
    - (void)testReplay {
        RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@1];
            [subscriber sendNext:@2];
            return nil;
        }] replay];  // 使用replay后上面block中的代码只执行一次
        
        [signal subscribeNext:^(id x) {
            NSLog(@"第一个订阅者%@",x);
        }];
        
        [signal subscribeNext:^(id x) {
            NSLog(@"第二个订阅者%@",x);
        }];
    }
    
    输出结果为:
    2017-06-30 14:39:10.989 RACDemo[76768:7350292] 第一个订阅者1
    2017-06-30 14:39:11.778 RACDemo[76768:7350292] 第一个订阅者2
    2017-06-30 14:39:12.394 RACDemo[76768:7350292] 第二个订阅者1
    2017-06-30 14:39:13.068 RACDemo[76768:7350292] 第二个订阅者2
    
    
    • retry 错误重试。订阅者sendError后重新执行信号block操作
    - (void)testRetry {
        __block int failedCount = 0;
        [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            if (failedCount < 5) {
                failedCount++;
                NSLog(@"我失败了");
                [subscriber sendError:nil];
            }else{
                NSLog(@"经历了五次失败后");
                [subscriber sendNext:nil];
            }
            return nil;
        }] retry] subscribeNext:^(id x) {
            NSLog(@"终于成功了");
        }];
    }
    
    输出结果为:
    2017-06-30 14:47:19.473 RACDemo[76899:7358317] 我失败了
    2017-06-30 14:47:19.474 RACDemo[76899:7358317] 我失败了
    2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失败了
    2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失败了
    2017-06-30 14:47:19.475 RACDemo[76899:7358317] 我失败了
    2017-06-30 14:47:19.475 RACDemo[76899:7358317] 经历了五次失败后
    2017-06-30 14:47:19.475 RACDemo[76899:7358317] 终于成功了
    
    另外:
    - (RACSignal *)retry:(NSInteger)retryCount; 方法可以控制重试的次数。
    
    
    • interval 定时。每隔一段时间激活一次信号
    - (void)testInterval {
        __block int num = 0;
        [[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) {
            num++;
            NSLog(@"count:%d", num);
        }];
    }
    
    输出结果为:
    2017-06-30 14:57:02.553 RACDemo[77081:7365782] count:1
    2017-06-30 14:57:03.553 RACDemo[77081:7365782] count:2
    2017-06-30 14:57:04.553 RACDemo[77081:7365782] count:3
    2017-06-30 14:57:05.553 RACDemo[77081:7365782] count:4
    2017-06-30 14:57:06.553 RACDemo[77081:7365782] count:5
    ···
    
    
    • throttle 节流。一定时间内不接收任何信号内容,一旦到达这个时间就获取最新最近的信号内容。
    - (void)testThrottle {
        [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            [subscriber sendNext:@"旅客A"];
            // 1秒时发送一条信息
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [subscriber sendNext:@"旅客B"];
            });
            // 1.5秒时发送一条信息
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [subscriber sendNext:@"插队旅客"];
            });
            // 2秒时发送三条信息
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [subscriber sendNext:@"旅客C"];
                [subscriber sendNext:@"旅客D"];
                [subscriber sendNext:@"旅客E"];
            });
            // 3秒时发送一条信息
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [subscriber sendNext:@"旅客F"];
            });
            return nil;
        }] throttle:1] subscribeNext:^(id x) {
            NSLog(@"%@通过了",x);
        }];
    }
    
    输出结果为:
    2017-06-30 15:10:44.948 RACDemo[77224:7375779] 旅客A通过了
    2017-06-30 15:10:46.947 RACDemo[77224:7375779] 旅客E通过了
    2017-06-30 15:10:48.046 RACDemo[77224:7375779] 旅客F通过了
    
    

    第五:MVVM + ReactiveCocoa

    实战开发中,ReactiveCocoa通常和MVVN设计模式结合使用,最快让你上手ReactiveCocoa之进阶篇 后半部分已经对其做了示例介绍,就不再赘述。

    希望本文章可以作为代码及原理查询手册,方便开发使用。
    最后,贴出两张速查表(第二张图在简书上放大也看不清,建议下载后放大查看),以示敬意~

    Signal森林.png ReactiveCocoa家族.png

    参考链接:

    最快让你上手ReactiveCocoa之基础篇
    ReactiveCocoa v2.5 源码解析之架构总览
    ReactiveCocoa基本组件:理解和使用RACCommand
    细说 ReactiveCocoa 的冷信号与热信号(三):怎么处理冷信号与热信号
    ReactiveCocoa源码阅读之RACScheduler
    RAC 中的双向数据绑定 RACChannel
    iOS ReactiveCocoa 最全常用API整理(可做为手册查询)
    ReactiveCocoa 中 RACScheduler是如何封装GCD的
    RAC核心元素与信号流
    这样好用的ReactiveCocoa,根本停不下来
    ReactiveCocoa操作方法(过滤,秩序,时间,重复)
    最快让你上手ReactiveCocoa之进阶篇
    RAC中combineLatest,zip,sample的测试
    RACSignal实践

    相关文章

      网友评论

      • 若幹年後:写的很不错啊 老铁
        麟young:@若幹年後 感谢老铁
      • 闻人慕学:大神,为啥我给tableViewCell上的一个UIView加了一个手势,在UIVIewController里面给cell上的手势绑定信号,x会有很多个手势的存在啊
        闻人慕学:@麟young 我试试!感谢
        麟young:不好意思 ,最近没上简书。初步判断原因是cell的复用机制导致信号被创建和监听多次,具体细节这篇文章中http://blog.csdn.net/chnvi/article/details/52606106会有些帮助。
      • Mr卿:mark。老铁 可以的
        麟young:感谢铁兄:smile:
      • 531576134d6f:老铁,你这篇文章好详细,我可以转发带博客吗?注明转发原地址。
        531576134d6f:@麟young 谢谢了老铁
        麟young:@什么都会一点的电动IT狗 可以 我写代码的时候忘了 也会过来搜一下直接用代码~
        531576134d6f:以后方便查阅

      本文标题:ReactiveCocoa之从小白到老铁

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