開發 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()即可。
不用在意要前往哪個畫面,也不用管如何推入。
這和直接 push、Segue 有什麼不同?
這部分常讓人混淆,整理成表格如下。
| 項目 | 直接 push / Segue | Coordinator 模式 |
|---|---|---|
| 切換邏輯位置 | 視圖控制器內 | 集中在 Coordinator |
| 畫面間相依性 | 彼此直接知道 | 不必知道彼此 |
| 畫面重複使用 | 困難 | 容易 |
| 掌握流程 | 散落於多個檔案 | 集中管理 |
| 初期工作量 | 少 | 稍多 |
如你所見,Coordinator 並不是萬能的。
如果只是只有兩三個畫面的小型 App,反而可能只是增加程式碼,得不償失。
相反地,登入、首次使用流程、分頁切換等流程分支多的 App,就很值得採用。
常見問題
Q. 畫面很多時,也要建立多個 Coordinator 嗎?
是。通常會依流程拆分,例如登入流程、主要流程。常見做法是由上層 Coordinator 管理子 Coordinator。
Q. 什麼時候要清理子 Coordinator?
流程結束時,父 Coordinator 必須從陣列移除子 Coordinator 的參照,否則會持續留在記憶體中並造成洩漏。
Q. SwiftUI 也會使用嗎?
SwiftUI 有以NavigationStack和path為基礎的路由,因此做法略有不同。不過,把流程抽成獨立物件的概念仍可沿用。
一開始多一個檔案,可能會覺得有些麻煩。
但當畫面增加到十個、二十個時,就會感受到這種結構的價值。
先從一個小型畫面流程開始,用 Coordinator 抽離它吧。你會親自感受到視圖控制器變得輕盈許多。🙂

