【iOS】MVVM+RxSwift+ReactorKit+Coordinator

MVVM + RxSwift

iOS 中的 MVVM 架構早就是個老生常談的問題,相比於傳統的 MVC 架構方式, MVVM 比較核心的地方在於雙向綁定的過程,即 ViewViewModel 之間的綁定,而創建綁定關係最優方案是經過響應式的方式構建,iOS 原生方面能夠經過 KVO + KVC 的方式去搭建響應式,缺點是API相對複雜,操做不方便,純 Swift 對象須要標記爲 dynamic ,須要手動管理 KVO 的生命週期。react

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 改變需經過 Actionide

  • 完善 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 的行爲更清晰明瞭。ui

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]()
    }
    
    複製代碼

完!

相關文章
相關標籤/搜索