開發 iOS App 時,總會遇到這種情況:同一個畫面混雜載入中、成功、失敗與無資料狀態。
大量建立 isLoading、hasError、isEmpty 這類 Bool 變數來管理時,變數越多就越像地獄。
明明正在載入,錯誤卻也是 true 的狀態。邏輯上說不通,但在程式碼中卻很容易產生這種組合。
先說結論:這種情況可以用一個 Swift enum 建立狀態機(FSM),不使用 State Pattern,讓狀態管理乾淨許多。
今天就用程式碼帶你看看如何脫離 Bool 地獄。
什麼是狀態機(FSM)?
不用想得太複雜。
狀態機 FSM(Finite State Machine)指的是:「目前可能存在的狀態數量是有限的,而且狀態只能依照既定規則轉換。」
想想紅綠燈就很容易理解。
綠燈 → 黃燈 → 紅燈。只能依照這個順序變化,不會從綠燈直接跳到紅燈。
App 畫面也是一樣。會從 로딩 → 성공 或 로딩 → 실패,不應存在同時是 성공 又是 로딩 的狀態。
但如果用多個 Bool 變數管理,程式碼就可能建立不應存在的狀態。
enum 正是用來阻止這件事。
使用 Swift enum 建立狀態機的方法
Swift 的 enum 不只是常數清單。
關鍵在於每個 case 都能攜帶值(associated value)。因此,一個 enum 就能同時容納狀態與資料。
我把畫面狀態定義如下。
enum LoadState {
case idle // 尚未執行任何動作
case loading // 載入中
case loaded([Item]) // 成功,包含資料
case failed(Error) // 失敗,包含錯誤
}
可以看到,loaded會攜帶項目陣列,failed則會攜帶錯誤。
成功時一定有資料,失敗時一定有錯誤。像「沒有資料卻成功」這種奇怪狀態根本無法建立。
在 View 中,只要查看這一個狀態就能繪製畫面。
switch state {
case .idle: EmptyView()
case .loading: ProgressView()
case .loaded(let items): ItemList(items)
case .failed(let error): ErrorView(error)
}
因為 switch 會強制處理所有 case,之後新增狀態時,編譯器也會立即指出尚未處理的地方。
這是非常大的優點:由編譯器而不是人來找出遺漏的狀態。
不使用 State Pattern 也可以嗎?
在物件導向教科書中,狀態管理通常會透過 State Pattern 來學習。
為每個狀態建立一個類別,用協定統整,再把轉換邏輯放進各個類別。
老實說,對畫面狀態管理而言,這通常有點過度設計。
我簡單比較了兩種方式。(以我 2026 年的實務經驗為準)
| 項目 | enum FSM | State Pattern |
|---|---|---|
| 檔案/型別數量 | 1 個 enum | 與狀態數量相同的類別 |
| 防止遺漏狀態 | 強制使用 switch,由編譯器找出問題 | 必須由人自行確認 |
| 攜帶資料 | 透過 associated value 自然處理 | 以屬性分開管理 |
| 適用規模 | 約 3~7 個狀態 | 每個狀態的邏輯非常複雜時 |
如果狀態數量適中,且各狀態的邏輯不重,enum FSM 會輕量且安全得多。
反之,如果每個狀態都包含數十行複雜行為,就可以考慮 State Pattern 或拆分成獨立物件。
並不是任何情況都有唯一正解。
如何管理狀態轉換?
你可能會問:只建立 enum 的話,不是可以任意切換到任何狀態嗎?
問得好。因此我會把轉換規則集中在一個函式中。
mutating func fetch() {
guard case .idle = self else { return } // idle只有在 idle 時才開始
self = .loading
}
guard case明確規定「只有目前是 idle 時,才能切換到載入中」。
把轉換條件集中在一處,狀態就會像紅綠燈一樣,只沿著既定路徑流動。
如此一來,之後重新查看程式碼時,也能一眼看出這個狀態可以從哪裡轉到哪裡。
Q. 如果狀態超過 5 個呢?
enum 仍然能應付。不過當轉換規則變複雜時,建議依狀態拆分轉換函式,分別整理。
Q. 和 SwiftUI 合得來嗎?
非常合適。將 enum 狀態放在 @State 或 @Published 中,再用 switch 繪製 View,狀態與畫面就能緊密同步。
一開始看似幾個 Bool 就夠了,但狀態開始互相糾結後,最後還是會回到 enum FSM。
如果你正為畫面狀態管理傷腦筋,就先試著替換成今天看到的 enum。你會發現比想像中更快變輕鬆。

