RxSwift with Coordinator & MVVM Patterns



在開始閱讀這篇文章之前,建議你先 學(xué)習(xí) RxSwift。


內(nèi)容概覽
  • Coordinator模式
  • MVVM模式
  • RxSwift with Coordinator & MVVM Patterns(內(nèi)含Demo)



Coordinator模式



你有沒有遭遇過這種情況?
你的ViewController之間相互耦合、依賴,導(dǎo)航邏輯分布散亂。

你是否聽過 Massive View Controller問題?
在MVC模式下,ViewController做了過多的工作,比如:初始化代碼、界面跳轉(zhuǎn)邏輯、業(yè)務(wù)邏輯等。

其中就一項(xiàng)任務(wù)本不該由ViewController來完成:屏幕導(dǎo)航的管理和應(yīng)用的執(zhí)行流程。


  • 什么是Coordinator模式?
    Coordinator模式提供了導(dǎo)航邏輯的封裝,由Soroush Khanlou在2015年的NSSpain會(huì)議上引入iOS社區(qū)。
    換句話說,所有的屏幕導(dǎo)航將由Coordinator來管理,而不是讓某個(gè)ViewController通過push或者present方法來顯示另一個(gè)ViewController。


  • 采用Coordinator模式有什么好處?
    采用Coordinator模式,ViewController之間被隔離開來,而且彼此不可見。
    這樣,就可以很方便地復(fù)用ViewController。




正如上面的圖解所示,Coordinator模式的核心思想可以歸納為以下幾點(diǎn):

  • 通過Coordinator協(xié)調(diào)一個(gè)或多個(gè)ViewController;
  • 每個(gè)Coordinator通過方法(通常叫作start方法)來顯示ViewControllers;
  • 每個(gè)ViewController都有一個(gè)delegate引用指向其Coordinator;
  • 每個(gè)Coordinator有一個(gè)childCoordinators數(shù)組來持有其子Coordinator;
  • 每個(gè)字Coordinator都有一個(gè)delegate引用指向其父Coordinator;


使用示例

Coordinator模式的實(shí)現(xiàn)有比較的方式,所以這里的示例僅供參考。

這里采用兩個(gè)Coordinator來管理3個(gè)ViewController,以此展示一個(gè)Coordinator可以有一個(gè)或多個(gè)ViewController。



首先,創(chuàng)建3個(gè)ViewController,F(xiàn)irstViewController, SecondViewController and ThirdViewController。
每個(gè)ViewController添加一個(gè)Button來導(dǎo)航到下一個(gè)ViewController。

首先,我們創(chuàng)建Coordinator協(xié)議。
協(xié)議包含一個(gè)childCoordinators數(shù)組和一個(gè)只接收NavigationController類型參數(shù)的init方法。

public protocol Coordinator : class {

    var childCoordinators: [Coordinator] { get set }

    // All coordinators will be initilised with a navigation controller
    init(navigationController:UINavigationController)

    func start()
}

然后第一個(gè)Coordinator會(huì)管理第一個(gè)ViewController。實(shí)現(xiàn)start方法,把第一個(gè)ViewController添加到NavigationController中。

import UIKit

class FirstCoordinator: Coordinator {
    
    var childCoordinators: [Coordinator] = []
    
    unowned let navigationController:UINavigationController
    
    required init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        let firstViewController : FirstViewController = FirstViewController()
        firstViewController.delegate = self
        self.navigationController.viewControllers = [firstViewController]
    }
}

extension FirstCoordinator: FirstViewControllerDelegate {

    // Navigate to next page
    func navigateToNextPage() {
       let secondCoordinator = SecondCoordinator(navigationController: navigationController)
       secondCoordinator.delegate = self
       childCoordinators.append(secondCoordinator)
       secondCoordinator.start()
    }
}

extension FirstCoordinator: BackToFirstViewControllerDelegate {
    
    // Back from third page
    func navigateBackToFirstPage(newOrderCoordinator: SecondCoordinator) {
        navigationController.popToRootViewController(animated: true)
        childCoordinators.removeLast()
    }
}

FirstCoordinator有兩個(gè)擴(kuò)展,第一個(gè)用于導(dǎo)航到下一個(gè)ViewController,第二個(gè)是導(dǎo)航回FirstCoordinator。
我們需要讓childCoordinators數(shù)組和當(dāng)前的Coordinator棧保持同步。


    var window: UIWindow?
    
    // Make the first coordinator with a strong reference
    var fisrtCoordinator : FirstCoordinator?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = UINavigationController()
        
        // Initialise the first coordinator with the main navigation controller
        fisrtCoordinator = FirstCoordinator(navigationController: window?.rootViewController as! UINavigationController)
        
        // The start method will actually display the main view
        fisrtCoordinator?.start()
        
        window?.makeKeyAndVisible()
        return true
    }

然后,以navigationController作為參數(shù)創(chuàng)建fisrtCoordinator。讓fisrtCoordinator的start來負(fù)責(zé)展示firstViewController。

public protocol FirstViewControllerDelegate: class {
    func navigateToNextPage()
}

class FirstViewController: UIViewController {

    public weak var delegate: FirstViewControllerDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "FirstViewController"
    }
    
    @IBAction func goToSecondPageAction(_ sender: Any) {
        self.delegate?.navigateToNextPage()
    }
}

現(xiàn)在,為了導(dǎo)航到下一個(gè)ViewController,我們需要通過delegate告訴Coordinator進(jìn)行導(dǎo)航。

protocol BackToFirstViewControllerDelegate: class {
    
    func navigateBackToFirstPage(newOrderCoordinator: SecondCoordinator)
    
}

class SecondCoordinator: Coordinator {
    
    var childCoordinators: [Coordinator] = []
    
    unowned let navigationController: UINavigationController
    
    // We use this delegate to keep a reference to the parent coordinator
    weak var delegate: BackToFirstViewControllerDelegate?
    
    required init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        let secondViewController: SecondViewController = SecondViewController()
        secondViewController.delegate = self
        self.navigationController.pushViewController(secondViewController, animated: true)
    }
}

extension SecondCoordinator : SecondViewControllerDelegate {
    
    // Navigate to third page
    func navigateToThirdPage() {
        let thirdViewController: ThirdViewController = ThirdViewController()
        thirdViewController.delegate = self
        self.navigationController.pushViewController(thirdViewController, animated: true)
    }
    
    // Navigate to first page
    func navigateToFirstPage() {
        self.delegate?.navigateBackToFirstPage(newOrderCoordinator: self)
    }
}

正如前文所述,一個(gè)Coordinator可以管理一個(gè)或多個(gè)ViewController。所以,這里讓secondCoordinator管理secondViewController 和 thirdViewController。

public protocol SecondViewControllerDelegate: class {
    func navigateToFirstPage()
    func navigateToThirdPage()
}

class SecondViewController: UIViewController {
    
    public weak var delegate: SecondViewControllerDelegate?

    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "SecondViewController"
        
        // Use custom back button to pass through coordinator.
        let backButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(navigateBackToFirstpage))
        self.navigationItem.leftBarButtonItem = backButton
    }
    
    @objc func navigateBackToFirstpage() {
        self.delegate?.navigateToFirstPage()
    }
    
    @IBAction func navigateToThirdPageAction(_ sender: Any) {
        self.delegate?.navigateToThirdPage()
    }
}
請注意:

在切換Coordinator時(shí),NavigationController的返回按鈕的點(diǎn)擊事件方法如果沒有被重寫的話,就會(huì)破壞Coordinator模式的邏輯。我們應(yīng)該重寫NavigationController返回按鈕的點(diǎn)擊事件方法來執(zhí)行Coordinator中的方法,以保證childCoordinators數(shù)組正常更新。



這里是ThirdViewController的實(shí)現(xiàn):

public class ThirdViewController: UIViewController {

    public weak var delegate: SecondViewControllerDelegate?
    
    override public func viewDidLoad() {
        super.viewDidLoad()

        title = "ThirdViewController"
    }

    @IBAction func navigateToFirstPageAction(_ sender: Any) {
        self.delegate?.navigateToFirstPage()
    }
}

最后,ThirdViewController調(diào)用代理方法可以回到FirstViewController。


結(jié)論

現(xiàn)在,我們可以通過Coordinator來管理ViewController之間的導(dǎo)航,不需要ViewController之間有任何相互引用。
將導(dǎo)航邏輯移到Coordinator中,有效地削弱了Massive ViewController效應(yīng)。



MVVM模式


  • 什么是MVVM模式?



Model:數(shù)據(jù)模型 或 數(shù)據(jù)持久層;
View:用戶交互界面層;
ViewModel:描述模型數(shù)據(jù)的狀態(tài),負(fù)責(zé)轉(zhuǎn)換Model中的數(shù)據(jù);
Binder:綁定View和ViewModel中對應(yīng)的屬性;


  • 采用MVVM模式有什么好處?

利于團(tuán)隊(duì)合作:設(shè)計(jì)師可以專注于用戶界面,開發(fā)者可以專注于業(yè)務(wù)邏輯。感興趣的話,請搜索 WPF MVVM;
利于代碼重用:模型、視圖被解耦,更容易復(fù)用;
利于自動(dòng)化測試:模塊耦合度低,模塊化測試容易實(shí)施;



RxSwift with Coordinator & MVVM



參考Demo的代碼倉庫

參考Demo的UML活動(dòng)圖

Demo采用了Coordinator模式和MVVM模式,部分代碼拷貝于RxSwift官方Demo。
此Demo僅供參考,歡迎讀者提出寶貴的建議,謝謝。






參考文章:
iOS : Coordinator pattern in Swift
Coordinator Tutorial for iOS: Getting Started (Raywenderlich Demo)
3 reasons to use the MVVM pattern
Coordinator 與 RxSwift 共同構(gòu)建的 MVVM 架構(gòu)
Observe Changes on TableView Cell with RxSwift and RxCocoa



如需轉(zhuǎn)載,請注明出處,謝謝 ~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容