ReactiveCocoa(RAC)

作者: 雷晏 | 来源:发表于2016-09-20 17:30 被阅读370次

    1.ReactiveCocoa简介

    ReactiveCocoa(简称RAC),是由Github开源的一个应用于IOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。

    2.ReactiveCocoa作用

    在我们IOS开发中,经常会响应某些事件来处理某些业务逻辑,例如按钮的点击、上下拉刷新、网络请求、属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。但是这些事件都用不同的方式来处理,比如action、delegate、KVO、callback等。

    其实这些事件,都可以通过RAC处理,ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把处理的事件和监听的事件的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高内聚、低耦合的思想。

    3.编程思想

    在开发中我们也不能太依赖于某个框架,否则这个框架不更新了,导致项目后期没办法维护,比如之前Facebook提供的Three20框架,在当时也是神器,但后来不更新了,也就没什么人用了。因此我感觉学习一个框架,还是有必要了解它的编程思想。

    先简单地介绍下目前我们已知的编程思想:

    3.1、面向过程:处理事物以过程为核心,一步一步地实现。

    3.2、面向对象:万物皆对象。

    3.3、链式编程思想:是将多个操作(多行代码)通过点语法链接在一起成为一句代码,是代码可读性好。

           链式编程特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)。

           代表:masonry框架

    3.4、响应式编程思想:不用考虑调用顺序,只需考虑调用结果。类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。

           代表:KVO运用

    3.5、函数式编程思想:是把操作尽量写成一系列嵌套的函数或者方法调用。

           函数式编程特点:每个方法必须有返回值(本身对象),把函数或者Block当做参数block(需要操作的值)block返回值(操作结果)

           代表:ReactiveCocoa

    4.ReactiveCocoa编程思想

    ReactiveCocoa结合了几种编程风格:

       函数式编程(Functional Programming)

       响应式编程(Reactive Programming)

    所以,你可能听说过ReactiveCocoa被描述为函数响应式(FRP)框架。

    以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高集合,方便管理。

    5.如何导入ReactiveCocoa框架

    从上图这样导入RAC框架会发现,会报错的。

    解决办法:加上 'use_frameworks!'

    6.ReactiveCocoa常见类

    学习框架首要之处:个人认为先要搞清楚框架中常用的类,在RAC中最核心的类RACSignal,搞定这个类就能用ReactiveCocoa开发了。

    RACSignal:信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。

            信号类(RACSignal),只是表示数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。

            默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。

            如何订阅信号:调用信号RACSignal的subscribeNext就能订阅。

    6.1.RACSignal的简单运用

        RACSignal使用步骤:

            1.创建信号:+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe

           2.发送信号:- (void)sendNext:(id)value (这步不一定有)

           3.订阅信号,才能激活信号:- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock

    RACSignal底层实现:

           1.创建信号,首先把didSubscribe保存到信号中,还不会触发

           2.当信号被订阅,也就是调用signal的subscribeNext:nextBlock

           2.subscribeNext内部会创建订阅者subscriber,并把nextBlock保存到subscriber中

           2.subscribeNext内部会调用signal的didSubscribe

           3.signal的didSubscribe中调用[subscriber sendNext:@1];

           3.1 sendNext底层其实就是执行了subscriber的nextBlock

    我们来创建一个信号并发送

    创建信号并发送

    我们运行程序可以发现,控制台并没有打印NSLog里的字符串,也印证了,创建的信号默认是个冷信号,需要我们手动去激活变成热信号,只有当信号被订阅了,才会调用。

    这里,我加上了订阅信号方法后,可以看到,控制台正常输出了想要的东西。 (注:信号内部发送数据,订阅方法就被调用;信号被订阅,信号内部就会被调用)

     RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵循这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助它发送数据。

     RACDisposable:用于取消订阅者或者清理资源,当信号发送完毕或者发送错误的时候,就会自动释放它。一般使用的地方:不想监听某个信号时,可以通过它来主动取消订阅者号。

         1.需要在信号被订阅的时候,返回一个RACDisposable,才能取消信号。

          2.只要订阅信号,就会返回一个取消信号的对象,通过subscribeNext订阅,返回来RACDisposable对象,再通过dispose来主动取消信号

    6.2.RACSubject

      信号提供者,自己可以充当信号,又能发送信号。rac中热信号的源头,继承于RACSignal

       RACSubject的使用步骤:

                1.创建信号 [RACSubject subject],跟RACSignal不一样,创建信号时没有block。

                2.订阅信号- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock

                3.发送信号- (void)sendNext:(id)value

        RACSubject底层实现和RACSignal不一样:

                 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值。

                 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock

        RACSubject的简单使用:

    单个订阅 多个订阅

    看log可以看出racsubject和racsignal有本质的区别,不关心历史值,错过就错过了,当发出第二个信号时,第一个订阅者收到的数据会相对于发生更新,所以不用去考虑历史值。

    6.3.RACReplaySubject

        重复提供信号类,RACSubject的子类。

        使用场景:

                   1.如果一个信号每被订阅一次,就需要把之前的值重新发送一遍,使用重复提供信号类。

                   2.可以设置capcity数量来限制缓存的value的数量,即只缓存最新的几个值

       RACReplaySubject的简单使用:

    只订阅1次 订阅2次

    可以看到log打印,把之前的值都重新发送了一遍。

    6.4.RACTuple和RACSequence

          RACTuple:元组类,类似NSArray,用来包装值

          RACSequence:RAC中的集合类,用来代替NSArray、NSDictionary,可以使用它来快速遍历数组和字典。

          使用场景:字典转模型

          RACSequence和RACTuple的简单使用

          主要有3个步骤 (numbers为数组)

                1.把数组转化成集合RACSequence    ->numbers.rac_sequence

                2.把集合RACSequence转化成RACSignal信号类      ->numbers.rac_sequence.signal

                3.订阅信号,激活信号,会自动把集合中的所有值遍历出来  ->subscribeNext

    RACSequence

    从log打印可以看到,遍历字典,有个RACTuple对象。那么可以通过RACTuple来处理,方便接下来直接取出key-value

    RACTuple1 RACTuple宏

         字典转模型

         通过map(映射)将数组遍历的集合映射成一个新值(模型)

         步骤:

                1. map:映射的意思,目的:把原始值value映射成一个新值

                2.toArray:把集合转成数组

                底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并保存到数组中

    字典转模型

    6.5.RACCommand

         RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,可以很方便的监控事件的执行过程。

         使用场景:监听按钮点击,网络请求

         使用步骤:

              1.创建命令- (id)initWithSignalBlock:(RACSignal * (^)(InputType input))signalBlock;

              2.在signalBlock中,创建RACSignal,并作为signalBlock的返回值。

              3.执行命令- (RACSignal *)execute:(InputType)input;

          注意事项:

             1.signalBlock必须返回一个信号,不能传nil

             2.如果不想传递信号,直接创建空的信号[RACSignal empty]

             3.RACCommand中信号如果数据传递完,调用[subscriber sendCompeted]来保证发送信号完毕,不会有更多的信号发送。

             4.RACCommand需要被强引用,否则接受不到RACCommand中的信号,因此RACCommand的信号是延迟发送的。

    从上图,可以看出,创建命令返回的是一个信号RACSignal,我们也知道信号可以发信号,所以,就有executionSignals信号源的概念

    executionSignals 信号源,包含事件处理的所有的信号

    定义:信号中发信号,也就是信号发出的数据也是信号。

    信号发送信号

    明白了信号发送信号的原理后,我们来看看executionSignals信号源如何去操作的。需要注意的是想要信号源接收到信号内容,需要将命令类强引用保持,保证命令类不被销毁。(在这里我就设置成全局)

    RACCommand->RACSignals

    6.6.RACMulticastConnection

           用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。

           使用注意:RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建

           使用步骤:

                  1.创建信号+ (RACSignal *)createSignal:(RACDisposable * (^)(idsubscriber))didSubscribe;

                  2.创建连接RACMulticastConnection *connect = [signal publish];

                  3.订阅信号,注意:订阅的信号不是之前的信号,而是连接的信号[connection.signal subscribeNext:nextBlock]

                4.连接 [connect connect];

           底层原理:

                1.创建connect,通过publish(multicast:)创建sourceSignal(指向原信号),connect.signal ->RACSubject

                2.订阅connect,会调用RACSubject的subscribeNext,创建订阅者,并把订阅者保存起来,并不会去去执行,也就是说现在的connect.signal信号是冷信号。

                3.[connect connect]内部会订阅RACSignal(原始信号),并且订阅者就是RACSubject,调用了此方法才能将信号变成热信号。

                3.订阅原始信号,就会调用didSubscribe

                3.didSubscribe后,拿到订阅者调用sendNext,其实就是调用RACSubjec的sendNext

                4.sendNext后,就会遍历所有订阅者发送信号

           使用场景:

                如果一个信号发出请求,每次订阅一次都会发出请求,这样导致多次请求,就可以利用RACMulticastConnection来解决

           简单使用:

    单独的订阅信号

    如果不使用连接,可以发现,每次去订阅一次信号,就会去重复调NSLog(),这个也就是副作用。

    加上连接Signal

    可以看到,就不会去重复请求了。

    7.ReactiveCocoa开发中常见用法

        1.代替代理:

                  ran_signalForSelector:用于代替代理

         场景:在控制器设置一个view,view里放个按钮button,然后点击button,让控制器知道按钮被点了。

        拓展:RACDelegateProxy 代理类

               这个类通常用在多个对象,但各种在代理方法中处理的事件不同,或者说实现不同的需求(注:RACDelegateProxy可能会被无缘无故地crash,所以需要强引用保留住)

    订阅信号返回block参数x就是当前操作的textField。这样写代理,符合了高内聚的思想,不必去到处找代理方法位置。

        2.代替KVO:

                  rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变

           场景:点击屏幕,年龄age++,来监听age变化值

        3.监听事件:

                  rac_signalForControlEvents:用于监听某个事件

        4.代替通知:

                 rac_addObserverForName:用于监听某个通知

             场景:监听键盘弹出

        5.监听文本框文字改变:

                rac_textSignal:只要文本框发出改变就会发出这个信号

        6.处理当界面有多次请求时,需要都获取到数据时,才能显示界面:

               rac_liftSelector:withSignalsFromArray:Signals:当传入的Signals(信号数组),每一个signal(都至少sendNext过一次,就会去触发第一个selector参数的方法)。

            场景:一个页面里,有2个不同的请求(分别是热门商品的请求和最新商品的请求),只有当2个请求都请求完毕后才更新界面。

    我们将最新商品发送信号方法senNext注释掉,会发现就不会去调更新UI方法了。

    8.ReactiveCocoa常见的宏

    总结:

       ReactiveCocoa框架是个重量级框架,学习起来确实比较费时间,学习一个框架,要去了解框架编程思想,比如Masonry框架,采取的是链式编程,ReactiveCocoa框架,采取的是函数响应式编程。框架固然方便了程序员,但不能太过于去依赖,如果有一天框架没更新了,写的代码就真的炸了,为此,我不推荐项目迭代时间较久的去使用第三方框架。

    相关文章

      网友评论

      本文标题:ReactiveCocoa(RAC)

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