Swift: Swift列挙型とパターンマッチングチュートリアル:基本の関連値から再帰的列挙型まで

Swiftの列挙型は「定数の集合」をはるかに超えています — データを持ち運び、メソッドをサポートし、自身を再帰的に定義することもできる第一級市民です。状態機械や結果型を構築するのに最適なツールです。

1. 学習目標


2. モバイル開発者の実話

(1) 課題:APIレスポンスの状態を表現するのが難しい

Aliceはニュースアプリのネットワーク層を構築中に厄介な問題に直面しました。APIリクエストは複数の結果を返す可能性があります:

SWIFT
// Inelegant approach: multiple optional variables to represent state
var data: NewsResponse? = nil
var error: Error? = nil
var isLoading = false

このアプローチには3つの問題があります:dataとerrorの両方が存在する場合の意味が曖昧状態の処理忘れがクラッシュを引き起こす散在したロジックが保守困難

(2) 関連値付き列挙型による解決策

単一の列挙型ですべての状態を統一し、各ケースが自身のデータを持ちます:

SWIFT
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

100%
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ステータスコード列挙型

SWIFT
// ============================================
// 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) 列挙型のメソッドとコンピューテッドプロパティ

列挙型は構造体と同様にメソッドとプロパティを定義できます。

▶ サンプル: メソッド付き列挙型

SWIFT
// ============================================
// 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) 関連値の構文

100%
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 関連値
値の型 固定定数 可変データ
格納タイミング 定義時に決定 作成時に受け渡し
ケースごと 全ケースが同じ値の型 ケースごとに異なれる
ユースケース 固定コードのマッピング 動的データの持ち運び

▶ サンプル: ネットワークリクエスト結果の処理

SWIFT
// ============================================
// 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 caseguard case を使って単一ケースのマッチングができます。

▶ サンプル: 簡潔な単一ケースマッチン��

SWIFT
// ============================================
// 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 { ... } 列挙型全体で再帰を許可

▶ サンプル: 数式の評価

SWIFT
// ============================================
// 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. 完全な例:注文状態機械

SWIFT
// ============================================
// 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)

❓ よくある質問

Q 列挙型と構造体の選び方は?
A 列挙型は「相互排他的な状態」に適します — 任意の時点で1つだけがアクティブになります。構造体は「複数の共存プロパティ」に適します — すべてのプロパティが同時に存在します。データモデルがOR関係かAND関係かで選択します。
Q 関連値とジェネリックは併用できますか?
A はい。enum Optional<T> は関連値 + ジェネリックの典型的な例です:case none または case some(T)。関連値はジェネリックパラメータを参照できます。
Q Swiftの列挙型がC/Javaの列挙型より強力な理由は?
A Swiftの列挙型はメソッド、コンピューテッドプロパ��ィ、関連値、再帰的定義、プロトコル準拠を持てます。C/Javaの列挙型は単なる名前付き整数定数ですが、Swiftの列挙型は完全な第一級型です。
Q indirectはいつ必ず使う必要がありますか?
A 列挙型の関連値の型が自身を参照する場合です。例:ArithmeticExpression.addition の関連値は ArithmeticExpression 型で、再帰的参照を形成するため indirect が必要です。
Q 列挙型のswitchでは常にdefaultを書く必要がありますか?
A すべてのケースを網羅し、defaultを使わないことを推奨します。こうすることで、将来新しいケースを追加した際にコンパイラが見落としを警告してくれ、意図しない漏れを防げます。ただし、本当にすべてのケースを処理する必要がない場合はdefaultを追加しても構いません。

📖 まとめ


📝 練習問題

  1. 基本: 赤、黄、緑の3つのケースを持つ TrafficLight 列挙型を定義してください。各信号の継続時間(秒)を返す duration コンピューテッドプロパティを追加してください。
  2. 中級: 関連値を使ってRustのResultに似た Either<L, R> 列挙型を定義し、left(L)right(R) ケースを持たせ、right値を変換する map メソッドを追加してください。
  3. 発展: 再帰的列挙型を使ってJSON型(null、bool、int、string、array、dictionaryをサポート)を実装し、JSON列挙型の値をインデント付き文字列としてフォーマットする関数を書いてください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%