iOSアプリを作っていると、いつか必ず遭遇します。1つの画面にローディング中、成功、失敗、データなしの状態が混在する瞬間です。
isLoadingやhasError、isEmptyのようなBool変数を大量に作って管理すると、変数が増えるほど地獄になります。
ローディング中なのにエラーもtrueの状態。論理的にはあり得なくても、コード上では簡単に作れてしまいます。
結論から言うと、こうした状況はステートパターン(State Pattern)を使わず、Swift enum 1つで状態機械(FSM)を作ると、ずっとすっきり整理できます。
今日は、コードと一緒にBool地獄から抜け出す方法を見ていきます。
状態機械(FSM)とは?
難しく考えなくて大丈夫です。
状態機械、FSM(Finite State Machine)とは、「現在あり得る状態数が決まっていて、定められたルールに従ってのみ状態が変わる」という考え方です。
信号機を思い浮かべると簡単です。
青 → 黄 → 赤。この順番でだけ変わり、青からいきなり赤にはなりません。
アプリの画面も同じです。로딩 → 성공または로딩 → 실패へ進み、성공であり同時に로딩でもある状態は存在してはいけません。
しかしBool変数を複数使うと、存在してはいけない状態もコード上で作れてしまいます。
そこを防いでくれるのがenumです。
Swift enumで状態機械を作る方法
Swiftのenumは、単なる定数の列挙ではありません。
各caseが値(associated value)を持てることがポイントです。1つのenumに状態とデータを一緒に保持できます。
画面の状態を次のように定義しました。
enum LoadState {
case idle // まだ何もしていない
case loading // 読み込み中
case loaded([Item]) // 成功、データ付き
case failed(Error) // 失敗、エラー付き
}
見ると、loadedはアイテム配列を、failedはエラーを一緒に保持しています。
成功なら必ずデータがあり、失敗なら必ずエラーがあります。データのない成功のような奇妙な状態は、そもそも作れません。
ビューでは、この1つの状態だけを見て画面を描画すればよいのです。
switch state {
case .idle: EmptyView()
case .loading: ProgressView()
case .loaded(let items): ItemList(items)
case .failed(let error): ErrorView(error)
}
switchがすべてのcaseの処理を強制するため、後から状態を追加しても、コンパイラーが未処理箇所をすぐ指摘してくれます。
これが大きな利点です。状態の抜け漏れを人ではなくコンパイラーが検出してくれます。
ステートパターンなしでも大丈夫?
オブジェクト指向の教科書では、状態管理は通常ステートパターンで学びます。
状態ごとにクラスを作ってプロトコルでまとめ、遷移ロジックを各クラスに入れる方法です。
正直に言うと、画面状態の管理には大げさなことが少なくありません。
2つの方法を簡単に比較しました。(2026年、私の実務経験に基づきます)
| 項目 | enum FSM | ステートパターン |
|---|---|---|
| ファイル/型の数 | enum 1つ | 状態数分のクラス |
| 状態の処理漏れ防止 | switchを強制し、コンパイラーが検出 | 人が自分で確認する必要がある |
| データの同梱 | associated valueで自然に扱える | プロパティとして別管理 |
| 適した規模 | 3~7状態程度 | 状態ごとのロジックが非常に複雑な場合 |
状態数が適度で、状態ごとのロジックが重くないなら、enum FSMのほうがずっと軽量で安全です。
逆に、各状態に数十行規模の複雑な動作が付くなら、ステートパターンや別オブジェクトへの分離を検討するとよいでしょう。
どちらか一方が常に正解というわけではありません。
状態遷移はどう管理する?
enumを作るだけでは、どの状態にも勝手に変わってしまうのではと思うかもしれません。
よい質問です。だから私は遷移ルールを1つの関数にまとめます。
mutating func fetch() {
guard case .idle = self else { return } // idleidleのときだけ開始
self = .loading
}
guard caseで「現在idleのときだけローディングへ進め」と明示しています。
遷移条件を1か所に置けば、信号機のように決められた道だけを状態が流れます。
そのため、後からコードを見返しても「この状態はどこからどこへ進めるのか」が一目で分かります。
Q. 状態が5つを超えたら?
それでもenumは十分対応できます。ただし遷移ルールが複雑になったら、遷移関数を状態ごとに細かく分けて整理するのがおすすめです。
Q. SwiftUIと相性はいい?
とても相性がいいです。enumの状態を@Stateや@Publishedに置き、switchでビューを描画すれば、状態と画面がぴったり連動します。
最初はBoolが数個あれば十分に見えても、状態が絡み始めると、結局enum FSMに戻ってきます。
画面状態の管理に悩んでいるなら、今日見たenumから少しずつ置き換えてみてください。思ったより早く楽になります。

