ReactorKit 是一个基于 RxSwift 的状态管理框架,通过它可以实现单向数据流架构。

ReactorKit 框架使用概述

ReactorKit 是一个基于 RxSwift 的状态管理框架,通过它可以实现单向数据流架构。

在这样的架构下,核心还是从 View 上发送 Action 给 Reactor(Reducer),再通过 Reactor 改变状态, 通过新的状态驱动视图更新。

使用上非常简单,在某个视图控制器上(由于交互都是在视图控制器上实现的)继承 View, 然后它就有一个 reactor 属性了, 外界将某个视图对应的 reactor 挂接到视图上, 即可实现单向数据流的状态管理.

当外部赋值 reactor 时, 会触发调用 bind 方法, 在 bind 里面实现的就是 action 和 state 之间的绑定.

Reactor 被规定为必须实现三个子类型: Action, Mutation, State, 这三个类型在 Reactor 中进行转换:

Action -> Mutation: func mutate(action: Action) -> Observable<Mutation>: 在 mutate 中执行相应的副作用操作, 如下所示:

swift
func mutate(action: Action) -> Observable<Mutation> {
  switch action {
  case let .refreshFollowingStatus(userID): // receive an action
    return UserAPI.isFollowing(userID) // create an API stream
      .map { (isFollowing: Bool) -> Mutation in
        return Mutation.setFollowing(isFollowing) // convert to Mutation stream
      }
  // 异步操作示例
  case let .follow(userID):
    return UserAPI.follow()
      .map { _ -> Mutation in
        return Mutation.setFollowing(true)
      }
  }
}

通过 Mutation 改变 State: func reduce(state: State, mutation: Mutation) -> State: reduce 必须为纯函数.

公共状态处理

在这样的架构中没有公共状态处理的固定形式, 一般来说, 公共状态处理时, 可以通过外部创建管理公共状态的流, 比如通过 BehaviorSubject, 然后在外部公共位置持有. 如果需要这些公共状态, 直接读取就行, 要使用的时候可以在某个视图的 reactor 中通过如下方式处理:

swift
func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
  return Observable.merge(mutation, currentUser.map(Mutation.setUser))
}

View 之间的通信

在父子视图之间进行通信, 可以像如下这样进行(设 MessageInputView 为某个 ChatViewController 的子视图, 它也可以有自己的 reactor, 只要它是 View 类型的话):

swift
extension Reactive where Base: MessageInputView {
  var sendButtonTap: ControlEvent<String> {
    let source = base.sendButton.rx.tap.withLatestFrom(...)
    return ControlEvent(events: source)
  }
}
swift
messageInputView.rx.sendButtonTap
  .map(Reactor.Action.send)
  .bind(to: reactor.action)

代码分析

Counter 示例

Counter 工程中在 AppDelegate 进行依赖注入:

swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  let viewController = self.window?.rootViewController as! CounterViewController
  viewController.reactor = CounterViewReactor()
  return true
}

在 CounterViewController 中主要实现的就是 bind 方法:

swift
 // Called when the new value is assigned to `self.reactor`
func bind(reactor: CounterViewReactor) {

    // View->Reactor

    // Action
    increaseButton.rx.tap               // Tap event
      .map { Reactor.Action.increase }  // Convert to Action.increase
      .bind(to: reactor.action)         // Bind to reactor.action
      .disposed(by: disposeBag)

    decreaseButton.rx.tap
      .map { Reactor.Action.decrease }
      .bind(to: reactor.action)
      .disposed(by: disposeBag)

    // Reactor->View

    // State
    reactor.state.map { $0.value }   // 10
      .distinctUntilChanged()
      .map { "\($0)" }               // "10"
      .bind(to: valueLabel.rx.text)  // Bind to valueLabel
      .disposed(by: disposeBag)

    reactor.state.map { $0.isLoading }
      .distinctUntilChanged()
      .bind(to: activityIndicatorView.rx.isAnimating)
      .disposed(by: disposeBag)
}

这样的架构中, 可以把 View 的状态和公共状态完全分离开来.

针对 Reactor 中的实现:

swift
final class CounterViewReactor: Reactor {
  // Action is an user interaction
  enum Action {
    case increase
    case decrease
  }

  // Mutate is a state manipulator which is not exposed to a view
  enum Mutation {
    case increaseValue
    case decreaseValue
    case setLoading(Bool)
  }

  // State is a current view state
  struct State {
    var value: Int
    var isLoading: Bool
  }

  let initialState: State

  init() {
    self.initialState = State(
      value: 0, // start from 0
      isLoading: false
    )
  }

  // Action -> Mutation
  func mutate(action: Action) -> Observable<Mutation> {
    switch action {
    case .increase:
      return Observable.concat([
        Observable.just(Mutation.setLoading(true)),
        Observable.just(Mutation.increaseValue).delay(.milliseconds(500), scheduler: MainScheduler.instance),
        Observable.just(Mutation.setLoading(false)),
      ])

    case .decrease:
      return Observable.concat([
        Observable.just(Mutation.setLoading(true)),
        Observable.just(Mutation.decreaseValue).delay(.milliseconds(500), scheduler: MainScheduler.instance),
        Observable.just(Mutation.setLoading(false)),
      ])
    }
  }

  // Mutation -> State
  func reduce(state: State, mutation: Mutation) -> State {
    var state = state
    switch mutation {
    case .increaseValue:
      state.value += 1

    case .decreaseValue:
      state.value -= 1

    case let .setLoading(isLoading):
      state.isLoading = isLoading
    }
    return state
  }
}

框架内部会在每个 Mutation 发射的时候调用 reduce, 通过 reduce 改变状态, 外部通过观察状态流从而实现视图更新, 所以一些重复性的工作都由框架帮忙完成了, 包括如下:

  1. 针对 Action 流中的事件去调用 mutate
  2. 针对 Mutation 流中的事件去调用 reduce
  3. 向外输出 State 流.

实际 Reactor 的输入就是 Action, 输出就是 State, 它们都以流的方式呈现.

使用步骤简化如下:

  1. 建立 View

  2. 建立 Reactor

  3. 注入 Reactor(属性注入)

  4. 在 View 中 bind 方法内将外部事件 Map 为 Reactor 可以接收的 Action, 再通过 bind 绑定到 Reactor 的 action 流上(在输入这条线上不会出现错误事件, 详见 ActionSubject 的实现):

    swift
    /// A special subject for Reactor's Action. It only emits `.next` event.
    public final class ActionSubject<Element>: ObservableType, ObserverType, SubjectType {
        // ...
    }
  5. 在 View 中的 bind 方法内将 Reactor 的输出 state 流按所需的属性进行 map 再绑定到某个具体视图上.

这样就完成整个流程, 相比手动实现的 MVVM 要方便很多.

特别是各层之间可以独立进行测试! View 可以 Mock, Reactor 可以 Mock, 当然服务层也可以单独测试.