軟體設計

使用 Swift enum 建立狀態機(FSM):不靠 State Pattern 管理狀態

開發 iOS App 時,總會遇到這種情況:同一個畫面混雜載入中、成功、失敗與無資料狀態。

閱讀 4 分鐘
使用 Swift enum 建立狀態機(FSM):不靠 State Pattern 管理狀態 封面圖

開發 iOS App 時,總會遇到這種情況:同一個畫面混雜載入中、成功、失敗與無資料狀態。

大量建立 isLoadinghasErrorisEmpty 這類 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。你會發現比想像中更快變輕鬆。

延伸閱讀