[Swift] enum の associated Value へのアクセス

Swift

定義された enum の associated value へのアクセス方法を説明します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Big Sur 11.2.1
  • Xcode 12.4
  • Swift5.3.2

記事中のコードは、Playground で動作します。

enum

class, struct と並んで、3大主要構成要素 (first class) です。

ですが、あまり積極的に使われていない気がするので、少し凝った使い方を説明します。

enum 基本編

enum の定義

他の言語と同様に、特定の有限数の集合を表現できます。

以下は、Size と名付けた enum の定義です

enum Size

enum Size {
        case S, M, L
}

Size という型を使うことで、S,M,L の値のいずれかを持っていることを保証できます。

enum を使わないとすると Int 型を使用して定義し、1がSを表し、2、3がM,Lを表すという運用をすることになりますが、Size を表す型は Int 型であるために、4や5が設定されることを言語的には防止できません。

enum を使うことで、そのような制約を課すことができ、特定値の集合をもつような型を安全に使用することが可能となります。

enum への表現としての値の割り当て

C 言語等では、内部的に数値が割り振られて管理されますが、swift では、自動的には割り振られません。

明示的に数値を割り当てたい時には、以下のように定義します。

Size with Int,RawRepresentable

enum Size:Int, RawRepresentable{
    case S = 1, M, L
}

Int に準拠すると宣言したことで、整数を割り振ることを宣言し、RawRepresentable に準拠すると宣言したことで、
以下のように、rawValue プロパティでアクセスすることができるようになります。

example for Size with Int,RawRepresentable

enum Size:Int, RawRepresentable{
    case S = 1, M, L   // S に 1 を割り当て、あとは列挙順に割り当てることを指定
}
let size = Size.M
print(size.rawValue)  // -> 2

上記のようにすることで、rawValue を +1 とすることで、サイズアップという操作にできます。(ただし、L の時にどうするかを検討しなければいけません)

この例のように enum の値が数値で表されることで、数値演算として処理できるようになり便利なることもあります。

Equatable

型として使うには、同値判定が必要となるケースがあります。

Equatable に準拠すると宣言するだけで、== で 同値判定を行うことができます。

enum Equatable example

enum Size:Int, RawRepresentable, Equatable{
    case S = 1, M, L
}
let size1 = Size.L
let size2 = Size.M
print(size1 == size2)    // -> false

ちなみに、RawRepresentable と宣言せずとも、Equatable に準拠させることができます。

enum Equatable example(2)

enum Size:Equatable{
    case S, M, L
}
let size1 = Size.L
let size2 = Size.M
print(size1 == size2)  // -> false

enum の associated value

Enum では特定値の集合を型として定義できるのですが、各値に対して、付加情報を追加することができます。associated value と呼ばれます。

associate value 付き enum の定義

以下は、Food という enum を定義し、種類(ピザ、ハンバーガー、おにぎり)を定義しています。associated value としてその量も保持できるようにしました。

example

enum Food {
    enum Size {
        case S, M, L
    }
    case pizza(Size)
    case hamburger(Int)
    case onigiri(Size,Int)
}

ピザはサイズ情報をもち、ハンバーガーは数を、おにぎりはサイズと数の情報を associated value として持っています。

このように、enum に属する値がそれぞれ異なるタイプの associated value を持つことができます。

Equatable

associated value を定義していても、準拠するだけで、Equatable になります。

example

enum Food:Equatable {
    enum Size: Equatable {
        case S, M, L
    }
    case pizza(Size)
    case hamburger(Int)
    case onigiri(Size,Int)
}
let food1 = Food.pizza(.L)
let food2 = Food.pizza(.M)
let food3 = Food.hamburger(3)
let food4 = Food.pizza(.M)
print(food1 == food2)    // -> false
print(food1 == food3)    // -> false
print(food2 == food4)    // -> true

associated value へのアクセス(switch)

Associated value を指定するときは、コンストラクタで指定するためわかりやすいと思います。

associated value 指定のイニシャライザ 例

let food1 = Food.pizza(.L)

Associated value へのアクセスは、以下のようになります。

food を受け取り1人分の食事として多すぎないかをチェックする関数を以下のように定義しました。

checkTooMuch

func checkTooMuch(food:Food) -> Bool{
    switch food {
        case .pizza(let size):            // pizza の size 情報を受け取る
            return size == .L
        case .hamburger(let num):         // hamburger の num 情報を受け取る
            return num >= 3
        case .onigiri(let size,let num):  // onigiri の size と num を受け取る
            switch size {
                case .L:
                    return num > 1
                default:
                    return num > 2
            }
    }
}

上記のように switch 文で受け取る際に、 associated value を変数に受け取ることができます。

switch 文で enum を使って分岐するときには、全ケースを列挙するか default を指定しないとコンパイルエラーになります。

この仕様は思わぬエラーを防止するために、非常に便利なのですが、すでに どの enum であるかの前提があり、associated value を受け取りたいだけの時には、少し面倒です。

associated value は、次のような形でもアクセスすることができます。

associated value へのアクセス (条件節)

以下のように書くことで、すべてのケースを網羅することなく、特定の enum 値であることを確認してアクセスすることができます。

以下は、先の switch 文で記述した関数と同じ動作をする関数です。

checkTooMuch(if)

func checkTooMuch(food:Food) -> Bool{
    if case Food.pizza(let size) = food {            // pizza の size を取得
        return size == Food.Size.L
    }
    if case Food.hamburger(let num) = food,          // hamburger の num を取得
       num >= 3 {
        return true
    }
    if case Food.onigiri(let size, let num) = food {  // onigiri の size と num を取得
        switch size {
            case .L:
                return num > 1
            default:
                return num > 2
        }
    }
    return false
}

上記は、if 文で書いている例になっていますが、guard でも同様に使用することができます。

MEMO
guard の条件節に記述した変数定義は、guard 内で使用することはできません。

Swift おすすめ本

Swift を深く理解するには、以下の本がおすすめです。

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です