ReactiveCocoa

函数式编程框架ReactiveCocoa

Posted by kunnan on December 26, 2016

前言

  • 对于一个应用来说,绝大部分的时间都是在等待某些事件的发生或响应某些状态的变化,
    比如用户的触摸事件、应用进入后台、网络请求成功刷新界面等等,而维护这些状态的变化,常常会使代码变得非常复杂,难以扩展。
    

iOS 中的 MVVM 可以分为以下三种不同的实践程度,它们分别对应不同的适用场景

  • MVVM + KVO
    MVVM + KVO ,适用于现有的 MVC 项目,想转换成 MVVM 但是不打算引入 RAC 作为 binder 的团队;
    
  • MVVM + RAC
    MVVM + RAC ,适用于现有的 MVC 项目,想转换成 MVVM 并且打算引入 RAC 作为 binder 的团队;
    
  • MVVM + RAC + ViewModel-Based Navigation
    MVVM + RAC + ViewModel-Based Navigation ,适用于全新的项目,想实践 MVVM 并且打算引入 RAC 作为 binder ,然后也想实践 ViewModel-Based Navigation 的团队。
    

ReactiveCocoa基础

  • Reactive Cocoa就是一个响应式编程的经典框架! ReactiveCocoa思维导图

MVVM数据绑定的实现方式(KVO、block、Delegate、Notification、RAC)

MVVM 的实现可以采用KVO进行数据绑定,也可以采用RAC。— 其实还可以采用block、代理(protocol)实现。

  • block 与protocol 相比的优点

  • KVO 与protocol、block相比的优点
    低耦合通信除了block、protocol之外,还有KVO。
    1)实现的方式中,KVO,不需要通过通知中心就可以进行属性值的监听;
    2)与bock以及代理相比,可以减少代理大量的方法和block大量的处理逻辑代码。
    
  • RAC 提供了优雅安全的数据绑定。
    1) 优雅集中体现在函数式编程。 
    2) 安全体现在响应式编程:使用RAC解决问题,就不需要考虑调用顺序。每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。
    

ReactiveCocoa简介

ReactiveCocoa ,就是用信号接管了iOS 中的所有事件;也就意味着,用一种统一的方式来处理iOS中的所有事件。

RAC的用途

  • ReactiveCocoa为事件提供了很多处理方法
    比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式来处理事件响应。其实这些事件,都可以通过RAC处理
    

  • 在 iOS 的 MVVM 实现中,我们可以使用 RAC 来在 view 和 viewModel 之间充当 binder 的角色,优雅地实现两者之间的同步。
  • 把 RAC 用在 model 层,使用 Signal 来代表异步的数据获取操作,比如读取文件、访问数据库和网络请求等。

RAC 体现的编程思想

  • RAC是函数响应式编程(FRP)框架。ReactiveCocoa结合了几种编程风格:函数式编程(Functional Programming)响应式编程(Reactive Programming)
  • ReactiveCocoa(简称为RAC),符合高聚合,低耦合的思想
    ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起(类似block),这样非常方便我们管理,就不需要跳到对应的方法里。
    

    ```

RAC 的特点

ReactiveCocoa编程思想

常见的编程思想

  • 1、面向过程:处理事情以过程为核心,一步一步的实现。

  • 2、面向对象:万物皆对象 、万物皆是流

    封装、继承、多态 
    1) 封装是为了更好的重用性、可扩展性,但要综合考虑性能问题,即使新增了判断也会增加性能,只是不会量级的增加。 
    比如简单工厂和抽象工厂(反射机制)的封装,会增加性能的消耗。但把对象的管理变成了可配置化。 
    

响应式编程思想

响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果。代表:KVO、ReactiveCocoa框架

  • 例子
    在程序开发中:
    Z = K + N
    赋值之后 K  或者 N  的值变化后,Z  的值不会跟着变化
    响应式编程,目标就是,如果 K  或者 N  的数值发生变化,Z  的数值会同时发生变化;
    

链式编程思想

  • 链式编程
例子

函数式编程思想

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

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

  • 实现:用函数式编程实现,写一个加法计算器,并且加法计算器自带判断是否等于某个值.

ReactiveCocoa 结合了这两种种编程风格

  • 函数式编程(Functional Programming)

  • 响应式编程(Reactive Programming)

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

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

导入ReactiveCocoa


ReactiveCocoa的GitHub地址

Objective-C

  • 关于 ReactiveCocoa 的版本演进历程,简单介绍如下:
    <= v2.5 :Objective-C ; 
    v3.x :Swift 1.2 ; 
    v4.x :Swift 2.x 。
    

ReactiveCocoa 2.5版本以后改用了Swift,所以Objective-C项目需要导入2.5版本

  • CocoaPods集成:
    platform :ios, '8.0'
    target 'YouProjectName' do
    use_frameworks!
    pod 'ReactiveCocoa', '~> 2.5'
    end
    

Swift

  • Swift项目导入最新版本
    platform :ios, '8.0'
    target 'YouProjectName' do
    use_frameworks!
    pod 'ReactiveCocoa'
    end
    
  • 头文件即可
#import <ReactiveCocoa/ReactiveCocoa.h>

ReactiveCocoa常见类

  • ReactiveCocoa就是根据 Monad 的概念搭建起来的
    一个 Monad 就是一种实现了 Monad typeclass 的数据类型。 
    

ReactiveCocoa 主要由以下四大核心组件构成:

  • 信号源:RACStream 及其子类;
    1)信号源又是最核心的部分,其他组件都是围绕它运作的
    2) 它使用信号来代表这些异步事件,提供了一种统一的方式来处理所有异步的行为,包括代理方法、block 回调、target-action 机制、通知、KVO 等:
    
  • 订阅者:RACSubscriber 的实现类及其子类;
  • 调度器:RACScheduler 及其子类;
  • 清洁工:RACDisposable 及其子类。

RACSiganl 信号类

  • 信号源代表的是随着时间而改变的值流。
    信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据
    
  • 注意:对于 RACSignal 类簇来说,最核心的方法莫过于 -subscribe
    1) 信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。
    2) 默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。
    -3) 如何订阅信号:调用信号RACSignal的subscribeNext就能订阅
    

    ```

  • RACSignal 代表的是未来将会被传送的值,它是一种 push-driven 的流。RACSignal 可以向订阅者发送三种不同类型的事件:

RACStream

  • RACStream 是 ReactiveCocoa 中最核心的类,代表的是任意的值流,它是整个 ReactiveCocoa 得以建立的基石,下面是它的继承结构图:

RACSubscriber

  • 表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。

  • 通过create创建的信号,都有一个订阅者,帮助他发送数据。

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;

RACDisposable

  • 用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
    它封装了取消和清理一次订阅所必需的工作。它有一个核心的方法 -dispose ,调用这个方法就会执行相应的清理工作,这有点类似于 NSObject 的 -dealloc 方法。
    

使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。

  • RACDisposable 总共有四个子类,它的继承结构图如下

RACSubject

RACSubject:信号提供者,自己可以充当信号,又能发送信号。

可以把它看作是 RACSignal 的可变版本

  • 在 MVVM 中使用 RACSubject 可以非常方便地实现统一的错误处理逻辑。

使用场景:通常用来代替代理,有了它,就不必要定义代理了。

RACReplaySubject

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

RACReplaySubjectRACSubject区别:

RACReplaySubject可以先发送信号,在订阅信号,RACSubject就不可以。

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

使用场景二:可以设置capacity数量来限制缓存的value的数量,即只缓充最新的几个值。

ACSubjectRACReplaySubject 简单使用:

  • RACSubject替换代理(与block类似)

RACTuple

元组类,类似NSArray,用来包装值.(@[key, value])

RACSequence

  • RACSequence 代表的是一个不可变的值的序列
    CSequence 并不能算作是信号源,因为它并不能像 RACSignal 那样,可以被订阅者订阅,但是它与 RACSignal 之间可以非常方便地进行转换
    
  • 从理论上说,一个 RACSequence 由两部分组成:
    head :指的是序列中的第一个对象,如果序列为空,则为 nil ;
    tail :指的是序列中除第一个对象外的其它所有对象,同样的,如果序列为空,则为 nil 。
    事实上,一个序列的 tail 仍然是一个序列。
    

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

RACCommand

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

RACMulticastConnection

  • 用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。
     // 需求:假设在一个信号中发送请求,每次订阅一次都会发送请求,这样就会导致多次请求。
      // 解决:使用RACMulticastConnection就能解决. 
    

RACScheduler

RAC中的队列,用GCD封装的。

RACUnit

表⽰stream不包含有意义的值,也就是看到这个,可以直接理解为nil.

RACEven

把数据包装成信号事件(signal event)。它主要通过RACSignal的-materialize来使用,然并卵。

ReactiveCocoa开发中常见用法

    1. 替换代理 : rac_signalForSelector:用于替代代理。
    1. 替换KVO: rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。
    1. 监听事件: rac_signalForControlEvents:用于监听某个事件。
    1. 替换通知: rac_addObserverForName:用于监听某个通知。
    1. 监听文本框文字改变: rac_textSignal:只要文本框发出改变就会发出这个信号。
    1. 统一处理多个网络请求:rac_liftSelector:withSignalsFromArray:Signals:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法。
      使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
      

替换代理:

rac_signalForSelector:

  • rac_signalForSelector: 直接监听 Selector 事件的调用
    [[self
      rac_signalForSelector:@selector(webViewDidStartLoad:)
      fromProtocol:@protocol(UIWebViewDelegate)]
      subscribeNext:^(id x) {
          // 实现 webViewDidStartLoad: 代理方法
      }];
    
  • RACSubject替换代理

替换KVO

  • rac_valuesForKeyPath:
// KVO
// 监听 slider 的 value 变化
[[self.slider rac_valuesForKeyPath:@"value" observer:nil] subscribeNext:^(id x) {

    NSLog(@"slider value Change:%@", x);
}];
  • RACObserve // KVO [RACObserve(self, username) subscribeNext:^(NSString *username) { // 用户名发生了变化 }];

替换通知

  • rac_addObserverForName

监听事件

rac_signalForControlEvents:

// 监听 btn 的 UIControlEventTouchUpInside 点击事件
[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    
    NSLog(@"btnTap");
}];

监听 textField 文字变化

rac_textSignal

[[self.textField rac_textSignal] subscribeNext:^(id x) {
        
	NSLog(@"textField change: %@", x);
}];

统一处理多个网络请求

  • rac_liftSelector:

注意

  • 替换KVO监听文本框文字改变 方法在创建监听方法时就会执行一次。
2016-12-28 16:53:50.746 ReactiveCacoa[4956:1246592] slider value Change:0.5
2016-12-28 16:53:50.748 ReactiveCacoa[4956:1246592] textField change:
  • 使用rac_liftSelector@selector(updateWithR1:R2:) 中的方 参数个数 要与 signal个数 相同,否则会被断言Crash!

see also

```

转载请注明: > ReactiveCocoa