iOS 中的 MVVM 架構早就是個老生常談的問題,相比於傳統的 MVC 架構方式, MVVM 比較核心的地方在於雙向綁定的過程,即 View 和 ViewModel 之間的綁定,而創建綁定關係最優方案是經過響應式的方式構建,iOS 原生方面能夠經過 KVO + KVC 的方式去搭建響應式,缺點是API相對複雜,操做不方便,純 Swift 對象須要標記爲 dynamic
,須要手動管理 KVO 的生命週期。react
RxSwift 屬於 ReactiveX 系列,目前存在多個語言版本,基本覆蓋所有主流編程語言,其專一於異步編程與控制可觀察數據(或者事件)流的API,背後是微軟的團隊在開發維護,因此穩定性較高。RxSwift 是一種響應式的編程思想,故很是適合與 MVVM 架構配合使用。編程
MVVM 架構最核心的部分無疑是 ViewModel ,其主要負責模塊的邏輯處理、狀態的維護等。在 iOS 開發中,狀態一詞說起的相對較少,不像 React 中組件對狀態的依賴那麼強烈。其實每個具備交互功能的控件都會依賴於狀態,狀態決定控件的表示方式,故狀態在 iOS 開發中一樣重要。傳統的 MVC 方式,狀態的管理主要是 Controller 負責,在 MVVM 中由 ViewModel 管理。網絡
ReactorKit 是一個輕量級的響應式框架,其依賴於 RxSwift 並結合了 Flux 。Flux 是 faceBook 提出的一種架構思想,其核心概念是數據的單向流動, 一樣適用於 iOS,在 iOS 中主要表現爲 Action 和 State 兩個部分:架構
View 發出的 Action,經由 Reactor 處理後,由 State 拋出後綁定到 View 上,即每個狀態的改變都要派發一個 Action 。也就是說 View 以怎樣的方式顯示是被動的,如想改變自身的渲染方式須要本身派發 Action。框架
ReactorKit 將 Controller 和 View 都歸類爲 View
,使用方式是須要實現 View
協議:異步
class ReactorViewController: UIViewController, View {
...
}
複製代碼
能夠理解爲 Reactor 就是 ViewModel 。Reactor 一樣是協議,其限定了 ViewModel 的行爲(代碼片斷來自網絡):編程語言
class ReactorViewModel: Reactor {
/// - Action: View 派發的事件
enum Action {
case refreshFollowingStatus(Int)
case follow(Int)
}
/// - Mutation:Action 和 State 之間的過渡
enum Mutation {
case setFollowing(Bool)
}
/// - State:狀態管理器
struct State {
var isFollowing: Bool = false
}
/// - 狀態管理器初始化
let initialState: State = State()
}
複製代碼
func mutate(action: Action) -> Observable<Mutation> {
switch action {
/// - View 派發的 Action 會在這裏被響應
case let .refreshFollowingStatus(userID):
return UserAPI.isFollowing(userID) // create an API stream
.map { (isFollowing: Bool) -> Mutation in
/// - 派發一個 Mutation
return Mutation.setFollowing(isFollowing)
}
/// - 同理
case let .follow(userID):
return UserAPI.follow()
.map { _ -> Mutation in
return Mutation.setFollowing(true)
}
}
func reduce(state: State, mutation: Mutation) -> State {
/// - 對 State 作一份拷貝,由於 State 是 let 聲明的結構體
var state = state
switch mutation {
/// - 將 Mutation 所關聯的數據映射到 State 上
case let .setFollowing(isFollowing):
/// - 改變 State
state.isFollowing = isFollowing
/// - 返回一個新的 State
return state
}
複製代碼
以上是 ViewModel 的大體工做流程:Action -> Mutatuin -> State,還有一些可選的API,如 transform(),可翻閱官方文檔查看。經過以上代碼可知,ReactorKit 符合 Flux 編程思想,簡單來講 State 改變需經過 Action。ide
衆所周知,MVVM 中 Controller 需持有 ViewModel,一樣 ReactorKit 中的 View 協議規定需顯示的指定 Reactor 的類型,並提供了 bind()
方法,能夠在這個方法中創建 View 和 ViewModel 之間的綁定關係。異步編程
class ReactorViewController: UIViewController, View {
func bind(reactor: ReactorViewModel) {
/// - View 發出的 Action 綁定到了 ViewModel(Reactor) 的 action 上
refreshButton.rx.tap.map { Reactor.Action.refresh }
.bindTo(reactor.action)
.addDisposableTo(self.disposeBag)
/// - ViewModel(Reactor) 的 State 綁定到了 View 上,並當即根據 State 渲染
reactor.state.map { $0.isFollowing }
.bindTo(followButton.rx.isSelected)
.addDisposableTo(self.disposeBag)
}
}
複製代碼
有了 ReactorKit 的協助,ViewModel 的行爲更清晰明瞭。ui
Coordinator 導航層。傳統的開發模式下,頁面間的跳轉是經過 navigationController
的 push()
方法,這種方法當然便捷,可是實現跳轉存在頁面間耦合。Coordinator 的誕生就是爲了解決這一問題,固然 路由 或者引入 中間管理層 也能夠實現解耦,但 Coordintaor 更輕量。
引入 Coordinator 後跳轉邏輯對頁面不可見,由 Coordinator 管理,其提供了 navigationController
的接口並持有 Controller,跳轉邏輯隱藏在了 Coordinator 中。Coordinator 獨立與 MVVM 以外,是一個附加層,不依賴於 MVVM 中的任何一部分。能夠理解爲 Coordinator 是每一個組件對外暴露的接口,當然頁面間的交互,只能經過 Coordinator,一樣依賴於 RxSwift。
/// - 頁面 Pop 後觸發的事件
enum PopResult {
case reload
case cancel
}
final class ExampleCoordinator: BaseCoordinator<PopResult> {
override func start() -> Observable<PopResult> {
let controller = ReactorViewController()
let reactor = ReactorViewModel()
let cancel = controller.popedAction
.asObserver()
.map { PopResult.cancel }
let delete = reactor.state
.map { $0.isDelete }
.filter { (bool) -> Bool in
return bool
}
.map { _ in PopResult.reload }
return Observable
.merge(cancel, delete)
.take(1).do(onNext: { [weak self] (result) in
if let `self` = self {
switch result {
case .reload:
self.navigationController.popViewController(animated: true)
break
default:
break
}
}
})
}
}
複製代碼
self.coordinate(to: ExampleCoordinator())
.subscribe(onNext: { result in
switch result {
case reload:
...
case cancel:
...
}
})
.disposed(by: self.disposeBag)
複製代碼
Coordinator 源碼一樣很簡單
public protocol CoordinatorType: NSObjectProtocol {
var identifier: UUID { get }
var childCoordinators: [UUID: Any] { get set }
var navigationController: RTRootNavigationController! { get set }
}
public extension CoordinatorType {
func store<T>(coordinator: BaseCoordinator<T>) {
coordinator.navigationController = navigationController
childCoordinators[coordinator.identifier] = coordinator
}
func free<T>(coordinator: BaseCoordinator<T>) {
childCoordinators[coordinator.identifier] = nil
}
@discardableResult
public func coordinate<T>(to coordinator: BaseCoordinator<T>) -> Observable<T> {
store(coordinator: coordinator)
return coordinator.start()
.do(onNext: { [weak self] _ in
if let `self` = self {
self.free(coordinator: coordinator)
}
})
}
}
public class BaseCoordinator<ResultType>: NSObject, UINavigationControllerDelegate, CoordinatorType {
/// Typealias which will allows to access a ResultType of the Coordainator by `CoordinatorName.CoordinationResult`.
typealias CoordinationResult = ResultType
public var navigationController: RTRootNavigationController!
func start() -> Observable<ResultType> {
fatalError("Coordinator Start method should be implemented by subclass.")
}
func noResultStart() {
fatalError("Coordinator noResultStart method should be implemented by subclass.")
}
/// UINavigationControllerDelegate
public func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
// ensure the view controller is popping
if let transitionCoordinator = navigationController.transitionCoordinator,
let fromViewController = transitionCoordinator.viewController(forKey: .from),
!navigationController.viewControllers.contains(fromViewController) {
fromViewController.popedAction.onNext(())
fromViewController.popedAction.onCompleted()
}
}
let disposeBag = DisposeBag()
public let identifier = UUID()
public var childCoordinators = [UUID: Any]()
}
複製代碼