本文最后更新于 2021年4月4日 晚上
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 中执行相应的副作用操作, 如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func mutate (action : Action ) -> Observable <Mutation > { switch action { case let .refreshFollowingStatus(userID): return UserAPI .isFollowing(userID) .map { (isFollowing: Bool ) -> Mutation in return Mutation .setFollowing(isFollowing) } 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 中通过如下方式处理:
1 2 3 func transform (mutation : Observable <Mutation >) -> Observable <Mutation > { return Observable .merge(mutation, currentUser.map(Mutation .setUser)) }
View 之间的通信 在父子视图之间进行通信, 可以像如下这样进行(设 MessageInputView 为某个 ChatViewController 的子视图, 它也可以有自己的 reactor, 只要它是 View 类型的话):
1 2 3 4 5 6 extension Reactive where Base : MessageInputView { var sendButtonTap: ControlEvent <String > { let source = base.sendButton.rx.tap.withLatestFrom(... ) return ControlEvent (events: source) } }
1 2 3 messageInputView.rx.sendButtonTap .map(Reactor .Action .send) .bind(to: reactor.action)
代码分析 Counter 示例 Counter 工程中在 AppDelegate 进行依赖注入:
1 2 3 4 5 func application (_ application : UIApplication , didFinishLaunchingWithOptions launchOptions : [UIApplication .LaunchOptionsKey : Any ]? ) -> Bool { let viewController = self .window? .rootViewController as! CounterViewController viewController.reactor = CounterViewReactor () return true }
在 CounterViewController 中主要实现的就是 bind
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func bind (reactor : CounterViewReactor ) { increaseButton.rx.tap .map { Reactor .Action .increase } .bind(to: reactor.action) .disposed(by: disposeBag) decreaseButton.rx.tap .map { Reactor .Action .decrease } .bind(to: reactor.action) .disposed(by: disposeBag) reactor.state.map { $0 .value } .distinctUntilChanged() .map { "\($0 ) " } .bind(to: valueLabel.rx.text) .disposed(by: disposeBag) reactor.state.map { $0 .isLoading } .distinctUntilChanged() .bind(to: activityIndicatorView.rx.isAnimating) .disposed(by: disposeBag) }
这样的架构中, 可以把 View 的状态和公共状态完全分离开来.
针对 Reactor 中的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 final class CounterViewReactor : Reactor { enum Action { case increase case decrease } enum Mutation { case increaseValue case decreaseValue case setLoading(Bool ) } struct State { var value: Int var isLoading: Bool } let initialState: State init () { self .initialState = State ( value: 0 , isLoading: false ) } 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 )), ]) } } 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
的实现):
1 2 3 4 public final class ActionSubject <Element >: ObservableType , ObserverType , SubjectType { }
在 View 中的 bind 方法内将 Reactor 的输出 state 流按所需的属性进行 map 再绑定到某个具体视图上.
这样就完成整个流程, 相比手动实现的 MVVM 要方便很多.
特别是各层之间可以独立进行测试! View 可以 Mock, Reactor 可以 Mock, 当然服务层也可以单独测试.