# 列挙型とは

列挙型とは、値型の一種で、関連する値のグループに共通の型を定義してまとめたものです。

# 列挙型の特徴

Swiftの列挙型は次のような特徴があります。

# 列挙型の定義方法

列挙型の定義方法は、enum キーワードと case キーワードを使用して定義します。

enum 型名 {
  case ケース名
  case ケース名
  case ケース名
}

次の例では、Animal型を作り、dog、cat、rabbitのケースを定義しています。

enum Animal {
  case dog
  case cat
  case rabbit
}

列挙型は、型.ケース でインスタンス化させることができます。

enum Animal {
  case dog
  case cat
  case rabbit
}

let dog = Animal.dog // dog
let cat = Animal.cat // cat

型が予めわかっている場合は、. だけで呼び出すこともできます。

enum Animal {
  case dog
  case cat
  case rabbit
}

// 型を定義
var a: Animal

// すでに型がわかっているので、Animalを省略して書ける
a = .dog // dog

# イニシャライザ

列挙型は、init キーワードを使ってイニシャライザを定義することができます。

enum Animal {
  case dog
  case cat
  case rabbit

  init?(animal: String) {
    switch animal {
    case "dog": self = .dog
    case "cat": self = .cat
    case "rabbit": self = .rabbit
    default: return nil
    }
  }
}

let dog = Animal(animal: "dog") // Optional(dog)
let hamster = Animal(animal: "hamster") // nil

イニシャライザで、引数に渡された値によって、各ケースを代入しています。ケースが存在しなければnilを返しています。

# プロパティ

列挙型のプロパティは、コンピューテッドプロパティタイププロパティ を使用することができます。プロパティの定義には制限があり、ストアドプロパティを使うことはできません。

enum Animal {
  case dog
  case cat
  case rabbit

  // コンピューテッドプロパティを定義する
  var feed: String {
    switch self {
    case .dog: return "お肉"
    case .cat: return "お魚"
    case .rabbit: return "にんじん"
    }
  }

  // タイププロパティを定義する
  static let limit = 100
}

Animal.dog.feed     // お肉
Animal.limit        // 100

ストアドプロパティを定義すると、コンパイラはエラーを出力します。

enum Animal {
  case dog
  case cat
  case rabbit

  var limit = 100
}

/* 実行結果 */
// error: enums must not contain stored properties

# メソッド

Swiftの列挙型は、メソッドを定義することができます。

enum Animal {
  case dog
  case cat
  case rabbit

  // メソッドを定義
  func sound() -> String {
    switch self {
    case .dog: return "bark"
    case .cat: return "meow"
    case .rabbit: return "boing-boing"
    }
  }
}

Animal.dog.sound()  // bark

switch文の中で、全てのケースが網羅されていないと、Swiftは整合性を保つためにエラーを出力します。

enum Animal {
  case dog
  case cat
  case rabbit

  func sound() -> String {
    switch self {
    case .dog: return "bark"
    case .cat: return "meow"

    // rabbitのケースがない
    // case .rabbit: return "boing-boing"
    }
  }
}

/* 実行結果 */
// error: switch must be exhaustive
//     switch self {
// note: add missing case: '.rabbit'

mutating メソッドを使うと列挙型自身を変更することができます。

enum Code: Int {
  case a
  case b
  case c
    
  // mutating で変更可能にする
  mutating func update(code: Code) {
    self = code
  }
}

var a = Code.a

a.update(code: .b)

a.rawValue // 1

# ネスト

列挙型は、ネストして記述することができます。ネストすることにより階層構造を明確にして、簡潔に書くことができます。

enum Animal {
  enum Dog {
    case bulldog
    case poodle
    case beagle
  }

  enum Cat {
    case benegal
    case britishShorthair
    case persian
  }
}

Animal.Dog.bulldog      // bulldog
Animal.Cat.benegal      // benegal

# 値型 enum | Raw value enum

列挙型のケースに設定する値をRaw valueといいます。Raw valueは全て同じ型でなければなりません。Int型、Double型、String型、Character型などを指定することができますが、その他の型を割り当てようとすると、コンパイラはエラーを出力します。

Raw valueは、ケースに値を代入して定義します。

enum Programming: Int {
    case swift = 1
    case ruby = 2
    case php = 3
    case javascript = 4
}

enum BloodType: String {
    case a = "A"
    case b = "B"
    case o = "O"
    case ab = "AB"
}

それ以外の型を指定すると、コンパイラはエラーを出力します。次の例では、構造体を代入しています。

struct Rectangle {
  var width: Int
  var height: Int
}

enum RectangleSize {
    case sm = Rectangle(width: 100, height: 100)
    case md = Rectangle(width: 200, height: 200)
    case lg  = Rectangle(width: 300, height: 300)
}

/* 実行結果 */
// error: raw value for enum case must be a literal
//     case sm = Rectangle(width: 100, height: 100)
              
// error: raw value for enum case must be a literal
//     case md = Rectangle(width: 200, height: 200)
             
// error: raw value for enum case must be a literal
//     case lg  = Rectangle(width: 300, height: 300)

# イニシャライザ

Raw valueが定義されると、暗黙的に rawValue を引数にとる init?(失敗可能イニシャライザ) と プロパティの rawValue が作られます。

そのため、インスタンス化のときに rawValue を引数に渡すと対応するケースが返ってきます。値は、ケース.rawValue で取得することができます。

enum BloodType: String {
    case a = "A"
    case b = "B"
    case o = "O"
    case ab = "AB"
}

// rawValue を引数に渡す
let a = BloodType(rawValue: "A")

// 値を取得する
a?.rawValue // A

// 直接アクセスしても取得できる
BloodType.a.rawValue // A

# デフォルト値

Raw valueに値を設定しなかった場合は、デフォルト値がセットされます。数値型の場合は、一番上のケースが0からインクリメントされ、String型の場合はケース名が値と同じになります。

enum Programming: Int {
    case swift
    case ruby
    case php
    case javascript
}

enum BloodType: String {
    case a
    case b
    case o
    case ab
}

Programming.swift.rawValue          // 0
Programming.ruby.rawValue           // 1
Programming.php.rawValue            // 2
Programming.javascript.rawValue     // 3

BloodType.a.rawValue                // a
BloodType.b.rawValue                // b
BloodType.o.rawValue                // o
BloodType.ab.rawValue               // ab

# 関連型 enum | Associated value enum

関連型 enum を使用すると、ケースに付属的に関連する値をつけることができます。関連値はあらかじめ決められて値を初期化しないので、値型 enumのように型の制限もありません。

次の例では、サイトに応じてレスポンシブにするブレイクポイントを定義しています。

enum MediaQuery {
  // ケースとそれに付属する値の型を定義
  case breakpoint(xs: Int, sm: Int, md: Int, lg: Int, xl: Int)
}

次に、定義したケースに対して具体的な値を設定します。サイトによってブレイクポイントは異なるので、別々の値を定義しています。

let breakpoint1 = MediaQuery.breakpoint(xs: 0, sm: 600, md: 960, lg: 1280, xl: 1920)
let breakpoint2 = MediaQuery.breakpoint(xs: 0, sm: 640, md: 1020, lg: 1480, xl: 1920)

定義した値を使うために、メソッドを定義しましょう。ケースからの値を抽出して配列形式で返します。

enum MediaQuery {
  case breakpoint(xs: Int, sm: Int, md: Int, lg: Int, xl: Int)

  // メソッドを定義
  func breakpoints() -> [Int] {
    switch self {
    case .breakpoint(let xs, let sm, let md, let lg, let xl):
      return [xs, sm, md, lg, xl]
    }
  }
}

そして、このメソッドを実行してケースの値を取得することができます。

breakpoint1.breakpoints() // [0, 600, 960, 1280, 1920]
breakpoint2.breakpoints() // [0, 640, 1020, 1480, 1920]

関連値を使うことで、柔軟にデータを作ることができました。関連型enumは、インスタンスごとに異なる値を持たせたい場合に使うのがいいでしょう。

# allCases

列挙型は、CaseIterable プロトコルに準拠させることで、ケースを配列で取得することができます。

次の例では、CaseIterable を準拠させて配列で取得しています。

enum Animal: CaseIterable {
  case dog
  case cat
  case rabbit
}

Animal.allCases // [dog, cat, rabbit]