ReactorKit 框架使用概述

本文最后更新于 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): // 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 中通过如下方式处理:

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
 // 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 中的实现:

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 {
// 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 的实现):

    1
    2
    3
    4
    /// 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, 当然服务层也可以单独测试.


ReactorKit 框架使用概述
https://blog.rayy.top/2020/09/19/2020-09-19-reactor-kit1/
作者
貘鸣
发布于
2020年9月19日
许可协议