iOS 工程

iOS Coordinator 模式:將畫面切換程式碼從視圖控制器中抽離

開發 iOS App 時,視圖控制器常常會逐漸變得臃腫。

閱讀 4 分鐘
iOS Coordinator 模式:將畫面切換程式碼從視圖控制器中抽離 封面圖

開發 iOS App 時,視圖控制器常常會逐漸變得臃腫。

繪製畫面的程式碼、資料處理,甚至切換到下一個畫面的pushViewController都混在一起。

放著不管,畫面流程越複雜,就越難追蹤從哪裡切換到哪裡。

今天就來看看能將畫面切換邏輯從視圖控制器乾淨抽離的iOS Coordinator 模式。

Coordinator 模式是在架構中加入一個專門負責畫面切換的獨立物件。視圖控制器只負責繪製畫面,下一步要去哪裡則交給 Coordinator。


什麼是 Coordinator 模式?

一句話定義如下。

Coordinator 是專門負責畫面切換(導覽)流程的物件。

原本視圖控制器要獨自處理畫面繪製、使用者輸入,以及切換到下一個畫面。

只要把其中的切換到下一個畫面抽出來,交給 Coordinator 即可。

按下按鈕時,視圖控制器只要把「按鈕已按下」這件事通知 Coordinator。

它不需要知道實際要去哪裡,也不用知道如何切換。

如此一來,視圖控制器彼此不必直接認識。A 畫面不需要知道 B 畫面的存在,重複使用也容易許多。


為什麼要抽離畫面切換?

把畫面切換程式碼放在視圖控制器裡,會產生不少問題。

以下整理我親身遇過的情況。

  • 相依性糾結:A 畫面直接建立 B 畫面,因此 B 改變時 A 也要一起修改。
  • 難以重複使用:想在其他流程使用同一畫面時,還得再次修改切換程式碼。
  • 難以掌握流程:畫面移動邏輯散落在 20 個檔案中,看不見全貌。
  • 難以測試:切換邏輯綁在視圖控制器上,很難單獨測試。

將切換邏輯集中到一處,就能解決大部分問題。

只要查看一個 Coordinator 檔案,就能看懂這個 App 的畫面流程。


如何建立 Coordinator?

基本結構比想像中簡單。

先定義一個作為共同規格的協定。start()就是起點。

protocol Coordinator: AnyObject {
    var navigationController: UINavigationController { get }
    func start()
}

接著建立實際的 Coordinator,負責建立並顯示第一個畫面。

final class MainCoordinator: Coordinator {
    let navigationController: UINavigationController
    init(nav: UINavigationController) { self.navigationController = nav }

    func start() {
        let vc = HomeViewController()
        vc.coordinator = self          // 接收切換請求的管道
        navigationController.pushViewController(vc, animated: false)
    }

    func showDetail() {                // 下一個畫面的切換只在這裡處理
        let vc = DetailViewController()
        navigationController.pushViewController(vc, animated: true)
    }
}

按鈕按下後,視圖控制器只要呼叫coordinator?.showDetail()即可。

按一下按鈕,剩下的交給 Coordinator 處理。
按一下按鈕,剩下的交給 Coordinator 處理。

不用在意要前往哪個畫面,也不用管如何推入。

只留下這一行呼叫,視圖控制器就輕鬆多了。
只留下這一行呼叫,視圖控制器就輕鬆多了。

這和直接 push、Segue 有什麼不同?

這部分常讓人混淆,整理成表格如下。

項目 直接 push / Segue Coordinator 模式
切換邏輯位置 視圖控制器內 集中在 Coordinator
畫面間相依性 彼此直接知道 不必知道彼此
畫面重複使用 困難 容易
掌握流程 散落於多個檔案 集中管理
初期工作量 少 稍多

如你所見,Coordinator 並不是萬能的。

如果只是只有兩三個畫面的小型 App,反而可能只是增加程式碼,得不償失。

相反地,登入、首次使用流程、分頁切換等流程分支多的 App,就很值得採用。

流程分支越多的 App,越能發揮價值。
流程分支越多的 App,越能發揮價值。

常見問題

Q. 畫面很多時,也要建立多個 Coordinator 嗎?

是。通常會依流程拆分,例如登入流程、主要流程。常見做法是由上層 Coordinator 管理子 Coordinator。

Q. 什麼時候要清理子 Coordinator?

流程結束時,父 Coordinator 必須從陣列移除子 Coordinator 的參照,否則會持續留在記憶體中並造成洩漏。

Q. SwiftUI 也會使用嗎?

SwiftUI 有以NavigationStack和path為基礎的路由,因此做法略有不同。不過,把流程抽成獨立物件的概念仍可沿用。


一開始多一個檔案,可能會覺得有些麻煩。

但當畫面增加到十個、二十個時,就會感受到這種結構的價值。

先從一個小型畫面流程開始,用 Coordinator 抽離它吧。你會親自感受到視圖控制器變得輕盈許多。🙂

延伸閱讀