【iOS】MVVM+RxSwift+ReactorKit+Coordinator

_coderYoung發表於2019-06-02

MVVM + RxSwift

iOS 中的 MVVM 架構早就是個老生常談的問題,相比於傳統的 MVC 架構方式, MVVM 比較核心的地方在於雙向繫結的過程,即 ViewViewModel 之間的繫結,而建立繫結關係最優方案是通過響應式的方式構建,iOS 原生方面可以通過 KVO + KVC 的方式去搭建響應式,缺點是API相對複雜,操作不方便,純 Swift 物件需要標記為 dynamic ,需要手動管理 KVO 的生命週期。

RxSwift 屬於 ReactiveX 系列,目前存在多個語言版本,基本覆蓋全部主流程式語言,其專注於非同步程式設計與控制可觀察資料(或者事件)流的API,背後是微軟的團隊在開發維護,所以穩定性較高。RxSwift 是一種響應式的程式設計思想,故非常適合與 MVVM 架構配合使用。

ViewModel + ReactorKit

MVVM 架構最核心的部分無疑是 ViewModel ,其主要負責模組的邏輯處理、狀態的維護等。在 iOS 開發中,狀態一詞提及的相對較少,不像 React 中元件對狀態的依賴那麼強烈。其實每一個具有互動功能的控制元件都會依賴於狀態,狀態決定控制元件的表示方式,故狀態在 iOS 開發中同樣重要。傳統的 MVC 方式,狀態的管理主要是 Controller 負責,在 MVVM 中由 ViewModel 管理。

ReactorKit 是一個輕量級的響應式框架,其依賴於 RxSwift 並結合了 FluxFlux 是 faceBook 提出的一種架構思想,其核心概念是資料的單向流動, 同樣適用於 iOS,在 iOS 中主要表現為 ActionState 兩個部分:

ReactorKit

View 發出的 Action,經由 Reactor 處理後,由 State 丟擲後繫結到 View 上,即每一個狀態的改變都要派發一個 Action 。也就是說 View 以怎樣的方式顯示是被動的,如想改變自身的渲染方式需要自己派發 Action

  • View

    ReactorKitControllerView 都歸類為 View,使用方式是需要實現 View 協議:

    class ReactorViewController: UIViewController, View { 
    ... 
    }
    複製程式碼
  • Reactor + State

    可以理解為 Reactor 就是 ViewModelReactor 同樣是協議,其限定了 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

  • 完善 View

    眾所周知,MVVMController 需持有 ViewModel,同樣 ReactorKit 中的 View 協議規定需顯示的指定 Reactor 的型別,並提供了 bind() 方法,可以在這個方法中建立 ViewViewModel 之間的繫結關係。

    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 的行為更清晰明瞭。

Coordinator

Coordinator 導航層。傳統的開發模式下,頁面間的跳轉是通過 navigationControllerpush() 方法,這種方法固然便捷,但是實現跳轉存在頁面間耦合。Coordinator 的誕生就是為了解決這一問題,當然 路由 或者引入 中間管理層 也可以實現解耦,但 Coordintaor 更輕量。

引入 Coordinator 後跳轉邏輯對頁面不可見,由 Coordinator 管理,其提供了 navigationController 的介面並持有 Controller,跳轉邏輯隱藏在了 Coordinator 中。Coordinator 獨立與 MVVM 之外,是一個附加層,不依賴於 MVVM 中的任何一部分。可以理解為 Coordinator 是每個元件對外暴露的介面,固然頁面間的互動,只能通過 Coordinator,同樣依賴於 RxSwift

  • 實現 Coordinator
/// - 頁面 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
                    }
                }
            })
    }
}
複製程式碼
  • Coordinator 互動(頁面跳轉)
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]()
    }
    
    複製程式碼

完!

相關文章