🐍

Swiftにおける列挙型の性質

2025/01/17に公開
2

はじめに

Enum(列挙型)は、特定の関連する値の集合を管理するための仕組みである。ここでは、Swiftを用いて Enum の基本的な性質や詳細な設定方法、処理の仕方について扱う。

基本的な性質

1. 固定された値の集合

Enumは一連の固定された値を表現するためのデータ型で、これらの値は通常、識別可能な名前を持つ。

例: 交通信号の状態

// この場合、TrafficLight 型には3つの状態( red 、yellow 、green )がある
enum TrafficLight {
    case red
    case yellow
    case green
}

let signal = TrafficLight.red
switch signal {
case .red:
    print("止まれ")
case .yellow:
    print("注意")
case .green:
    print("進んでも良い")
}

2. 型安全性

Enumは型安全であり、指定した値以外は許容されない。この型安全性によって、不正な値が使用されることを防げる。

例: 平日を表す列挙型

enum WeekDay {
    case monday, tuesday, wednesday, thursday, friday
}

let today: WeekDay = .sunday // エラー

3. 原始値(Raw Value)

多くのプログラミング言語で、Enumは内部的に整数値や文字列を持ち、これを 原始値(Raw Value)と呼ぶ。

例: 原始値を持つEnum

enum Color: Int {
    case red = 1
    case green = 2
    case blue = 3
}

let favoriteColor = Color.green
print(favoriteColor.rawValue) // 2

このように、各ケースに対して数値などを割り当てたり利用したりできる。

詳細な設定

1. 原始値の自動割り当て

Swiftでは、Enumの rawValue を設定する際、型を指定すれば自動で原始値が割り当てられる。以下に、自動割り当てが可能な場合と不可能な場合をまとめる。

自動割り当てが可能な型

整数型

最初のケースに値を明示的に指定すると、それ以降のケースには前の値に +1 した値が自動的に割り当てられる。値を指定しない場合、最初のケースは 0 から始まる。

enum Planet: Int {
    case mercury
    case venus
    case earth
    case mars
}

print(Planet.mars.rawValue) // 3

浮動小数点型

整数型と同様に、前の値に対して +1.0 した値が自動的に割り当てられる。

enum Measurement: Double {
    case small = 1.5
    case medium
    case large
}

print(Measurement.medium.rawValue) // 2.5

文字列型

各ケース名がそのまま文字列として自動的に割り当てられる。

enum Planet: String {
    case mercury
    case venus
    case earth
    case mars
}

print(Planet.venus.rawValue) // "venus"

自動割り当てが不可能な型

Date型やUUID型などの型では自動割り当てはサポートされておらず、すべてのケースに対して明示的に値を割り当てる必要がある。

2. 関連値(Associated Values)

各ケースに値を関連付けることができ、より柔軟に情報を保持できる。

例: 交通チケット

enum Ticket {
    case bus(price: Double, route: Int)         // バス: 路線番号
    case train(price: Double, destination: String) // 電車: 到着地
    case flight(price: Double, code: String)   // 飛行機: フライトコード
}

let ticket = Ticket.train(price: 14000.0, destination: "Osaka")

switch ticket {
case .bus(let price, let route):
    print("バス: 路線番号 \(route), 料金 \(price) 円")
case .train(let price, let destination):
    print("電車: \(destination) 行き, 料金 \(price) 円")
case .flight(let price, let code):
    print("飛行機: フライト \(code), 料金 \(price) 円")
}

3. 再帰型

enumは再帰型として定義でき、自己参照的なケースを持つことができる。

例: フォルダ構造の深さ

enum FileSystem {
    case file(name: String)
    indirect case folder(name: String, contents: [FileSystem]) // 再帰するケースにのみ`indirect`を付ける
}

let folder = FileSystem.folder(name: "Documents", contents: [
    .file(name: "Resume.pdf"),
    .folder(name: "Photos", contents: [
        .file(name: "Vacation.jpg"),
        .file(name: "Family.jpg")
    ])
])

func countFiles(_ fileSystem: FileSystem) -> Int {
    switch fileSystem {
    case .file:
        return 1
    case .folder(_, let contents):
        return contents.reduce(0) { $0 + countFiles($1) }
    }
}

print(countFiles(folder)) // 3

4. メソッドの追加

Enumにメソッドを追加して、機能を持たせることができる。

例: 方角の逆方向を返すメソッドを追加

enum CompassPoint {
    case north, south, east, west

    func opposite() -> CompassPoint {
        switch self {
        case .north:
            return .south
        case .south:
            return .north
        case .east:
            return .west
        case .west:
            return .east
        }
    }
}

let direction = CompassPoint.east
let oppositeDirection = direction.opposite()
print("現在の方向: \(direction)")
print("逆方向: \(oppositeDirection)")

5. 静的な値の保持

Swiftのenumは、静的なプロパティやメソッドを持つことができる。これにより、全体で共有される値やロジックを定義できる。

enum UserRole {
    case admin
    case editor
    case viewer

    // 各役割ごとの許可されたファイル操作
    static let permissions: [UserRole: [FileOperation]] = [
        .admin: FileOperation.allCases, // Adminはすべての操作が可能
        .editor: [.read, .write],       // Editorは読み書きのみ可能
        .viewer: [.read]                // Viewerは読み込みのみ可能
    ]

    // ファイル操作が許可されているかを確認
    func canPerform(operation: FileOperation) -> Bool {
        return UserRole.permissions[self]?.contains(operation) ?? false
    }
}

enum FileOperation: CaseIterable {
    case read 
    case write  
    case delete 
}

// ユーザーの役割ごとに権限を確認
let adminRole = UserRole.admin
let editorRole = UserRole.editor
let viewerRole = UserRole.viewer

print("Adminが削除可能: \(adminRole.canPerform(operation: .delete))") // true
print("Editorが書き込み可能: \(editorRole.canPerform(operation: .write))") // true
print("Viewerが読み込み可能: \(viewerRole.canPerform(operation: .read))") // true
print("Viewerが削除可能: \(viewerRole.canPerform(operation: .delete))") // false

処理の仕方

1. switch

switch 文によって対応する case に合わせた処理へ分岐させる。

enum Vehicle {
    case car(model: String, year: Int)
    case bike(brand: String)
}

let myVehicle = Vehicle.car(model: "Tesla", year: 2021)

switch myVehicle {
    case .car(let model, let year):
    print("車種: \(model), 年式: \(year)")
    case .bike(let brand):
    print("バイクブランド: \(brand)")
}

2. @unknown default(Swift 5以降)

@unknown を付けることで、列挙型に新しいケースが追加されたときにコンパイラが警告を出すようになる。列挙型が拡張される場合に備えて、 @unknown default を使うことで安全なコードを書くことができる。

例: ユーザー状態の処理

enum UserStatus {
    case active
    case inactive
    case banned
    case adjourned
}

func handleUserStatus(status: UserStatus) {
    // Switch must be exhaustive; this is an error in the Swift 6 language mode
    // Swift5ではこの警告が出る。Swift6では、Switch must be exhaustiveというエラーとなる。
    switch status {
    case .active:
        print("ユーザーはアクティブです")
    case .inactive:
        print("ユーザーは非アクティブです")
    case .banned:
        print("ユーザーはバンされています")
    @unknown default:
        print("未知の状態です")
    }
}

handleUserStatus(status: .adjourned)
// 未知の状態です

3. Equatable

Swiftのenumは自動的にEquatableに準拠するため、列挙型のインスタンス同士を比較することができる。

例: ログイン状態のチェック

enum LoginState {
    case loggedIn
    case loggedOut
    case inProgress
}

let currentState = LoginState.loggedIn

if currentState == .loggedIn {
    print("ユーザーはログイン中です")
} else {
    print("ユーザーはログインしていません")
}

4. 配列などにして高階関数と組み合わせる

列挙型を配列などの形式にして、filtermapなどの高階関数と組み合わせて扱うことで簡潔で可読性の高い操作が可能になる。

enum FileOperation: String {
    case read = "Read File"
    case write = "Write File"
    case delete = "Delete File"
}

let allOperations: [FileOperation] = [.read, .write, .delete]

// すべての操作を説明付きの文字列に変換
let allOperationDescriptions = allOperations.map { $0.rawValue }
print(allOperationDescriptions)
// 出力: ["Read File", "Write File", "Delete File"]

// 削除操作を除外し、許可された操作だけを説明付きの文字列に変換
let allowedOperations = allOperations.filter { $0 != .delete }
let allowedDescriptions = allowedOperations.map { $0.rawValue }
print(allowedDescriptions)
// 出力: ["Read File", "Write File"]

まとめ

Enumは、型安全性を保ちながら、関連する値を一元管理できるようにする仕組みである。

Discussion

hiragramhiragram

こんにちは!
@unknown default が導入されたのはSwift5からのようです!

torakotorako

こんにちは!
Playgroundで確認させていただいて、Swift5から導入されていることが確認できました。コメントいただき、ありがとうございます。
Swift5では警告、Swift6ではエラーとして認識されるみたいですね。

【Swift 5】

【Swift 6】