ReactorKit 是一个响应式、单向 Swift 应用框架。下面来介绍一下 ReactorKit 当中的基本概念和使用方法。react
ReactorKit 是 Flux 和 Reactive Programming 的混合体。用户的操做和视图 view 的状态经过可被观察的流传递到各层。这些流是单向的:视图 view 仅能发出操做(action)流 ,反应堆仅能发出状态(states)流。git
View 用来展现数据。 view controller 和 cell 均可以看作一个 view。view 须要作两件事:(1)绑定用户输入的操做流,(2)将状态流绑定到 view 对应的 UI 元素。view 层没有业务逻辑,只负责绑定操做流和状态流。github
定义一个 view,只须要将一个现存的类符合协议 View
。而后这个类就自动有了一个 reactor
的属性。view 的这个属性一般由外界设置。swift
class ProfileViewController: UIViewController, View {
var disposeBag = DisposeBag()
}
profileViewController.reactor = UserViewReactor() // inject reactor
复制代码
当这个 reactor
属性被设置(或修改)的时候,将自动调用 bind(reactor:)
方法。view 经过实现 bind(reactor:)
来绑定操做流和状态流。闭包
func bind(reactor: ProfileViewReactor) {
// action (View -> Reactor)
refreshButton.rx.tap.map { Reactor.Action.refresh }
.bind(to: reactor.action)
.disposed(by: self.disposeBag)
// state (Reactor -> View)
reactor.state.map { $0.isFollowing }
.bind(to: followButton.rx.isSelected)
.disposed(by: self.disposeBag)
}
复制代码
若是使用 storyboard 来初始一个 view controller,则须要使用 StoryboardView
协议。StoryboardView
协议和 View
协议相比,惟一不一样的是 StoryboardView
协议是在 view 加载结束以后进行绑定的。app
let viewController = MyViewController()
viewController.reactor = MyViewReactor() // will not executes `bind(reactor:)` immediately
class MyViewController: UIViewController, StoryboardView {
func bind(reactor: MyViewReactor) {
// this is called after the view is loaded (viewDidLoad)
}
}
复制代码
反应堆 Reactor 层,和 UI 无关,它控制着一个 view 的状态。reactor 最主要的做用就是将操做流从 view 中分离。每一个 view 都有它对应的反应堆 reactor,而且将它全部的逻辑委托给它的反应堆 reactor。框架
定义一个 reactor 时须要符合 Reactor
协议。这个协议要求定义三个类型: Action
, Mutation
和 State
,另外它须要定义一个名为 initialState
的属性。异步
class ProfileViewReactor: Reactor {
// represent user actions
enum Action {
case refreshFollowingStatus(Int)
case follow(Int)
}
// represent state changes
enum Mutation {
case setFollowing(Bool)
}
// represents the current view state
struct State {
var isFollowing: Bool = false
}
let initialState: State = State()
}
复制代码
Action
表示用户操做,State
表示 view 的状态,Mutation
是 Action
和 State
之间的转化桥梁。reactor 将一个 action 流转化到 state 流,须要两步:mutate()
和 reduce()
。ide
mutate()
mutate()
接受一个 Action
,而后产生一个 Observable<Mutation>
。测试
func mutate(action: Action) -> Observable<Mutation>
复制代码
全部的反作用应该在这个方法内执行,好比异步操做,或者 API 的调用。
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)
}
}
}
复制代码
reduce()
reduce()
由当前的 State
和一个 Mutation
生成一个新的 State
。
func reduce(state: State, mutation: Mutation) -> State
复制代码
这个应该是一个简单的方法。它应该仅仅同步的返回一个新的 State
。不要在这个方法内执行任何有反作用的操做。
func reduce(state: State, mutation: Mutation) -> State {
var state = state // create a copy of the old state
switch mutation {
case let .setFollowing(isFollowing):
state.isFollowing = isFollowing // manipulate the state, creating a new state
return state // return the new state
}
}
复制代码
transform()
transform()
用来转化每一种流。这里包含三种 transforms()
的方法。
func transform(action: Observable<Action>) -> Observable<Action>
func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
func transform(state: Observable<State>) -> Observable<State>
复制代码
经过这些方法能够将流进行转化,或者将流和其余流进行合并。例如:在合并全局事件流时,最好使用 transform(mutation:)
方法。点击查看全局状态的更多信息。
另外,也能够经过这些方法进行测试。
func transform(action: Observable<Action>) -> Observable<Action> {
return action.debug("action") // Use RxSwift's debug() operator
}
复制代码
和 Redux 不一样, ReactorKit 不须要一个全局的 app state,这意味着你能够使用任何类型来管理全局 state,例如用 BehaviorSubject
,或者 PublishSubject
,甚至一个 reactor。ReactorKit 不须要一个全局状态,因此无论应用程序有多特殊,均可以使用 ReactorKit。
在 Action → Mutation → State 流中,没有使用任何全局的状态。你能够使用 transform(mutation:)
将一个全局的 state 转化为 mutation。例如:咱们使用一个全局的 BehaviorSubject
来存储当前受权的用户,当 currentUser
变化时,须要发出 Mutation.setUser(User?)
,则能够采用下面的方案:
var currentUser: BehaviorSubject<User> // global state
func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
return Observable.merge(mutation, currentUser.map(Mutation.setUser))
}
复制代码
这样,当 view 每次向 reactor 产生一个 action 或者 currentUser
改变的时候,都会发送一个 mutation。
多个 view 之间通讯时,一般会采用回调闭包或者代理模式。ReactorKit 建议采用 reactive extensions 来解决。最多见的 ControlEvent
示例是 UIButton.rx.tap
。关键思路就是将自定义的视图转化为像 UIButton 或者 UILabel 同样。
假设咱们有一个 ChatViewController
来展现消息。 ChatViewController
有一个 MessageInputView
,当用户点击 MessageInputView
上的发送按钮时,文字将会发送到 ChatViewController
,而后 ChatViewController
绑定到对应的 reactor 的 action。下面是 MessageInputView
的 reactive extensions 的一个示例:
extension Reactive where Base: MessageInputView {
var sendButtonTap: ControlEvent<String> {
let source = base.sendButton.rx.tap.withLatestFrom(...)
return ControlEvent(events: source)
}
}
复制代码
这样就是能够在 ChatViewController
中使用这个扩展。例如:
messageInputView.rx.sendButtonTap
.map(Reactor.Action.send)
.bind(to: reactor.action)
复制代码
ReactorKit 有一个用于测试的 built-in 功能。经过下面的指导,你能够很容易测试 view 和 reactor。
首先,你要肯定测试内容。有两个方面须要测试,一个是 view 或者一个是 reactor。
view 能够根据 stub reactor 进行测试。reactor 有一个 stub
的属性,它能够打印 actions,而且强制修改 states。若是启用了 reactor 的 stub,mutate()
和 reduce()
将不会被执行。stub 有下面几个属性:
var isEnabled: Bool { get set }
var state: StateRelay<Reactor.State> { get }
var action: ActionSubject<Reactor.Action> { get }
var actions: [Reactor.Action] { get } // recorded actions
复制代码
下面是一些测试示例:
func testAction_refresh() {
// 1. prepare a stub reactor
let reactor = MyReactor()
reactor.stub.isEnabled = true
// 2. prepare a view with a stub reactor
let view = MyView()
view.reactor = reactor
// 3. send an user interaction programatically
view.refreshControl.sendActions(for: .valueChanged)
// 4. assert actions
XCTAssertEqual(reactor.stub.actions.last, .refresh)
}
func testState_isLoading() {
// 1. prepare a stub reactor
let reactor = MyReactor()
reactor.stub.isEnabled = true
// 2. prepare a view with a stub reactor
let view = MyView()
view.reactor = reactor
// 3. set a stub state
reactor.stub.state.value = MyReactor.State(isLoading: true)
// 4. assert view properties
XCTAssertEqual(view.activityIndicator.isAnimating, true)
}
复制代码
reactor 能够被单独测试。
func testIsBookmarked() {
let reactor = MyReactor()
reactor.action.onNext(.toggleBookmarked)
XCTAssertEqual(reactor.currentState.isBookmarked, true)
reactor.action.onNext(.toggleBookmarked)
XCTAssertEqual(reactor.currentState.isBookmarked, false)
}
复制代码
一个 action 有时会致使 state 屡次改变。好比,一个 .refresh
action 首先将 state.isLoading
设置为 true
,并在刷新结束后设置为 false
。在这种状况下,很难用 currentState
测试 state
的 isLoading
的状态更改过程。这时,你能够使用 RxTest 或 RxExpect。下面是使用 RxExpect 的测试案例:
func testIsLoading() {
RxExpect("it should change isLoading") { test in
let reactor = test.retain(MyReactor())
test.input(reactor.action, [
next(100, .refresh) // send .refresh at 100 scheduler time
])
test.assert(reactor.state.map { $0.isLoading })
.since(100) // values since 100 scheduler time
.assert([
true, // just after .refresh
false, // after refreshing
])
}
}
复制代码
定义 scheduler
属性来指定发出和观察的状态流的 scheduler
。注意:这个队列 必须 是一个串行队列。scheduler
的默认值是 CurrentThreadScheduler
。
final class MyReactor: Reactor {
let scheduler: Scheduler = SerialDispatchQueueScheduler(qos: .default)
func reduce(state: State, mutation: Mutation) -> State {
// executed in a background thread
heavyAndImportantCalculation()
return state
}
}
复制代码
其余信息能够查看 github