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 中执行相应的副作用操作, 如下所示:
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 中通过如下方式处理:
func transform(mutation: Observable<Mutation>) -> Observable<Mutation> { return Observable.merge(mutation, currentUser.map(Mutation.setUser)) }
View 之间的通信
在父子视图之间进行通信, 可以像如下这样进行(设 MessageInputView 为某个 ChatViewController 的子视图, 它也可以有自己的 reactor, 只要它是 View 类型的话):
extension Reactive where Base: MessageInputView { var sendButtonTap: ControlEvent<String> { let source = base.sendButton.rx.tap.withLatestFrom(...) return ControlEvent(events: source) } }
messageInputView.rx.sendButtonTap .map(Reactor.Action.send) .bind(to: reactor.action)
代码分析
Counter 示例
Counter 工程中在 AppDelegate 进行依赖注入:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { let viewController = self.window?.rootViewController as! CounterViewController viewController.reactor = CounterViewReactor() return true }
在 CounterViewController 中主要实现的就是 bind 方法:
// 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 中的实现:
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 改变状态, 外部通过观察状态流从而实现视图更新, 所以一些重复性的工作都由框架帮忙完成了, 包括如下:
- 针对 Action 流中的事件去调用 mutate
- 针对 Mutation 流中的事件去调用 reduce
- 向外输出 State 流.
实际 Reactor 的输入就是 Action, 输出就是 State, 它们都以流的方式呈现.
使用步骤简化如下:
-
建立 View
-
建立 Reactor
-
注入 Reactor(属性注入)
-
在 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 { // ... } -
在 View 中的 bind 方法内将 Reactor 的输出 state 流按所需的属性进行 map 再绑定到某个具体视图上.
这样就完成整个流程, 相比手动实现的 MVVM 要方便很多.
特别是各层之间可以独立进行测试! View 可以 Mock, Reactor 可以 Mock, 当然服务层也可以单独测试.