Swift: Swift列挙型とパターンマッチングチュートリアル:基本の関連値から再帰的列挙型まで
Swiftの列挙型は「定数の集合」をはるかに超えています — データを持ち運び、メソッドをサポートし、自身を再帰的に定義することもできる第一級市民です。状態機械や結果型を構築するのに最適なツールです。
1. 学習目標
- 基本的な列挙型の構文とRaw Valueの使用
- 関連値が列挙型に追加データを持たせる方法
- 様々なパターンマッチングスタイル(switch、if-case、guard-case)
- indirectキーワードによる再帰的列挙型
- 状態機械における列挙型の実践的な応用
2. モバイル開発者の実話
(1) 課題:APIレスポンスの状態を表現するのが難しい
Aliceはニュースアプリのネットワーク層を構築中に厄介な問題に直面しました。APIリクエストは複数の結果を返す可能性があります:
// Inelegant approach: multiple optional variables to represent state
var data: NewsResponse? = nil
var error: Error? = nil
var isLoading = false
このアプローチには3つの問題があります:dataとerrorの両方が存在する場合の意味が曖昧、状態の処理忘れがクラッシュを引き起こす、散在したロジックが保守困難。
(2) 関連値付き列挙型による解決策
単一の列挙型ですべての状態を統一し、各ケースが自身のデータを持ちます:
enum NetworkResult<T> {
case success(T)
case failure(Error)
case loading
}
NetworkResult<[NewsArticle]> は記事データ(成功)、エラー情報(失敗)、または読み込み中の状態のいずれかを持ちます — 3つの相互排他的な状態で意味が明確です。
(3) 利点:状態管理が混沌から明瞭に
| 観点 | 複数のオプショナル変数 | 関連値付き列挙型 |
|---|---|---|
| 状態数 | 2^N の不正な組み合わせ | N個の正確な状態 |
| 意味の明確さ | data/errorが共存しうる | 各状態が自身のデータを持つ |
| 処理の網羅性 | ケースの見落としが発生しやすい | switchの網羅性チェック |
| コード保守性 | if-elseに散在 | 1つの型に集中 |
3. 列挙型の基本
列挙型は関連する値の集合を定義します。Swiftの列挙型は他の言語よりも柔軟で — メソッド、コンピューテッドプロパティ、サブスクリプトを関連付けられます。
(1) 基本的な列挙型とRaw Value
graph LR
A["enum Compass {\n case north\n case south\n case east\n case west\n}"] --> B["Compass.north"]
A --> C["Compass.south"]
A --> D["Compass.east"]
A --> E["Compass.west"]
Raw Valueは各列挙型ケースの背後にある固定値で、StringやIntなどの型が使えます。
| Raw Value型 | 宣言 | 値 |
|---|---|---|
| Int | enum Status: Int { case ok = 200 } |
整数 |
| String | enum Direction: String { case north = "N" } |
文字列 |
| 暗黙的Int | enum Grade: Int { case a, b, c } |
0から増分 |
| 暗黙的String | enum Code: String { case red, green } |
ケース名と同じ |
▶ サンプル: HTTPステータスコード列挙型
// ============================================
// HTTP status code enum using raw values
// ============================================
enum HTTPStatus: Int {
case ok = 200
case created = 201
case badRequest = 400
case unauthorized = 401
case notFound = 404
case serverError = 500
}
let status = HTTPStatus.notFound
print("Code: \(status.rawValue)")
print("Name: \(status)")
// Create from raw value
let parsed = HTTPStatus(rawValue: 200)
print("Parsed: \(parsed ?? .serverError)")
出力:
TEXT 📖 参照専用Code: 404 Name: notFound Parsed: ok
(2) 列挙型のメソッドとコンピューテッドプロパティ
列挙型は構造体と同様にメソッドとプロパティを定義できます。
▶ サンプル: メソッド付き列挙型
// ============================================
// Custom enum method: determine status category
// ============================================
enum HTTPStatus: Int {
case ok = 200
case created = 201
case badRequest = 400
case unauthorized = 401
case notFound = 404
case serverError = 500
var isSuccess: Bool {
rawValue >= 200 && rawValue < 300
}
var description: String {
switch self {
case .ok: return "OK"
case .created: return "Created"
case .badRequest: return "Bad Request"
case .unauthorized: return "Unauthorized"
case .notFound: return "Not Found"
case .serverError: return "Internal Server Error"
}
}
}
let code = HTTPStatus.ok
print("\(code.rawValue) \(code.description) - Success: \(code.isSuccess)")
出力:
TEXT 📖 参照専用200 OK - Success: true
4. 関連値とパターンマッチング
関連値はSwiftの列挙型を他言語と差別化する中核機能です — 各ケースが異なる型の追加データを持てます。
(1) 関連値の構文
graph TB
A["NetworkResult<T>"] --> B["success(T) - データを持つ"]
A --> C["failure(Error) - エラーを持つ"]
A --> D["loading - データなし"]
B --> E["switch result {\n case .success(let data):\n show(data)\n case .failure(let error):\n show(error)\n case .loading:\n showSpinner()\n}"]
| 特徴 | Raw Value | 関連値 |
|---|---|---|
| 値の型 | 固定定数 | 可変データ |
| 格納タイミング | 定義時に決定 | 作成時に受け渡し |
| ケースごと | 全ケースが同じ値の型 | ケースごとに異なれる |
| ユースケース | 固定コードのマッピング | 動的データの持ち運び |
▶ サンプル: ネットワークリクエスト結果の処理
// ============================================
// Associated value enum for handling API responses
// ============================================
enum NetworkResult<T> {
case success(T)
case failure(String)
case loading
}
func handleResponse<T>(_ result: NetworkResult<T>) {
switch result {
case .success(let data):
print("Successfully received data: \(data)")
case .failure(let error):
print("Request failed: \(error)")
case .loading:
print("Loading...")
}
}
handleResponse(NetworkResult.success("User Info"))
handleResponse(NetworkResult.failure("Network connection timed out"))
handleResponse(NetworkResult.loading)
出力:
TEXT 📖 参照専用Successfully received data: User Info Request failed: Network connection timed out Loading...
(2) if-caseとguard-caseのパターンマッチング
switch以外にも、if case と guard case を使って単一ケースのマッチングができます。
▶ サンプル: 簡潔な単一ケースマッチン��
// ============================================
// Concise usage of if-case and guard-case
// ============================================
enum Measurement {
case weight(Double)
case height(Double)
case count(Int)
}
let record = Measurement.weight(75.5)
// if-case: only care about one case
if case .weight(let kg) = record, kg > 70 {
print("Weight exceeds 70kg: \(kg)")
}
// guard-case: early exit
func process(_ m: Measurement) {
guard case .count(let n) = m else {
print("Not a count type")
return
}
print("Count is \(n)")
}
process(.weight(65))
process(.count(42))
出力:
TEXT 📖 参照専用Weight exceeds 70kg: 75.5 Not a count type Count is 42
5. 再帰的列挙型
再帰的列挙型とは、関連値が列挙型自体を参照する列挙型で、木構造やネスト構造の表現に適しています。
(1) indirectキーワード
indirect を使って再帰的なケースや列挙型全体をマークし、間接ストレージが必要であることをコンパイラに伝えます。
| 構文 | 説明 |
|---|---|
indirect case expression(Expression) |
単一の再帰的ケース |
indirect enum Expression { ... } |
列挙型全体で再帰を許可 |
▶ サンプル: 数式の評価
// ============================================
// Recursive enum: math expression tree
// ============================================
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
func evaluate(_ expr: ArithmeticExpression) -> Int {
switch expr {
case .number(let value):
return value
case .addition(let left, let right):
return evaluate(left) + evaluate(right)
case .multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
// Build the expression (3 + 5) * 2
let three = ArithmeticExpression.number(3)
let five = ArithmeticExpression.number(5)
let sum = ArithmeticExpression.addition(three, five)
let product = ArithmeticExpression.multiplication(sum, .number(2))
print("(3 + 5) * 2 = \(evaluate(product))")
出力:
TEXT 📖 参照専用(3 + 5) * 2 = 16
6. 完全な例:注文状態機械
// ============================================
// Full example: E-commerce order state machine
// Features: order state transitions + data processing
// ============================================
import Foundation
// 1. Order status enum (with associated values)
enum OrderStatus {
case pending // Pending payment
case paid(Date) // Paid (with payment time)
case shipped(trackingNumber: String) // Shipped (with tracking number)
case delivered(Date) // Delivered (with receipt time)
case cancelled(reason: String) // Cancelled (with reason)
var description: String {
switch self {
case .pending: return "Pending"
case .paid(let date): return "Paid (\(date.formatted()))"
case .shipped(let tracking): return "Shipped (Tracking: \(tracking))"
case .delivered(let date): return "Delivered (\(date.formatted()))"
case .cancelled(let reason): return "Cancelled: \(reason)"
}
}
var isFinalState: Bool {
if case .delivered = self { return true }
if case .cancelled = self { return true }
return false
}
}
// 2. Usage example
let now = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let statuses: [OrderStatus] = [
.pending,
.paid(now),
.shipped(trackingNumber: "SF1234567890"),
.delivered(now),
.cancelled(reason: "Cancelled by user")
]
for status in statuses {
let final = status.isFinalState ? " (Final)" : ""
print("\(status.description)\(final)")
}
出力:
TEXT 📖 参照専用Pending Paid (2026-07-30) Shipped (Tracking: SF1234567890) Delivered (2026-07-30) (Final) Cancelled: Cancelled by user (Final)
❓ よくある質問
enum Optional<T> は関連値 + ジェネリックの典型的な例です:case none または case some(T)。関連値はジェネリックパラメータを参照できます。ArithmeticExpression.addition の関連値は ArithmeticExpression 型で、再帰的参照を形成するため indirect が必要です。📖 まとめ
- Swiftの列挙型は第一級型で、メソッド、コンピューテッドプロパティ、プロトコル準拠をサポートする
- Raw Valueは各ケースに固定の定数値をバインドする
- 関連値は各ケースが異なる型の追加データを持てるようにする
- パターンマッチング(switch、if-case、guard-case)で列挙型データを安全に抽出する
- 再帰的列挙型はindirectキーワードを使って木構造のネスト構造を表現する
- 列挙型は状態機械、結果型、オプショナル型の構築に最適なツール
📝 練習問題
- 基本: 赤、黄、緑の3つのケースを持つ
TrafficLight列挙型を定義してください。各信号の継続時間(秒)を返すdurationコンピューテッドプロパティを追加してください。 - 中級: 関連値を使ってRustのResultに似た
Either<L, R>列挙型を定義し、left(L)とright(R)ケースを持たせ、right値を変換するmapメソッドを追加してください。 - 発展: 再帰的列挙型を使ってJSON型(null、bool、int、string、array、dictionaryをサポート)を実装し、JSON列挙型の値をインデント付き文字列としてフォーマットする関数を書いてください。