1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Swift】型の種類〜列挙型〜

Posted at

#列挙型 とは
列挙方は値型の一種で、複数の識別子をまとめる型になります。

曜日を例にしますと、月火水木金土日の7種類がありますが、
列挙型ではこれら7つの識別子をまとめて一つの型として扱えます。

列挙型の一つ一つの識別子をケースと言います。

また、火曜日であると同時に木曜日であることがありえないように、
ケースどうしは排他的になっています。

標準ライブラリでも、一部の型は列挙型として扱われています。
例えばよく登場するものでいうと、Optional<Wrapped>型が列挙型になります。

##定義方法

列挙型はenumキーワードcaseキーワードを使用し定義します。


enum 列挙型名 {
   case ケース名1
   case ケース名2
   ・・・
   そのほかの列挙型の定義
}

列挙型のインスタンス化は構造体やクラスと少し違い、
列挙型名.ケース名のようにケース名を指定してインスタンス化します。

次のサンプルコードでは、列挙型Weekdayに各曜日を定義し、
.mondayと.fridayをインスタンス化しています。


enum Weekday {
    case sunday
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturdar
}

let monday = Weekday.monday
let friday = Weekday.friday

また、列挙型もイニシャライザを定義することができます。
イニシャライザを追加し、引数に渡された文字列に応じて各ケースを代入します。


enum Weekday {
    case sunday
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturdar
    
    init?(japanese: String) {
        switch japanese {
        case "日": self = .sunday
        case "月": self = .monday
        case "火": self = .tuesday
        case "水": self = .wednesday
        case "木": self = .thursday
        case "金": self = .friday
        case "土": self = .saturdar
        default: return nil
        }
    }
}

let sunday = Weekday(japanese: "日")   // Optional<Weekday.sunday>
let monday = Weekday(japanese: "月")   // Optional<Weekday.monday>

列挙型では、イニシャライザの他にもメソッドやプロパティを持つことはできますが、
プロパティには制限があり、ストアドプロパティを持つことができません。

つまり、列挙型はコンピューテッドプロパティしか持つことができないのです。

##raw value(ローバリュー)
列挙型は、それぞれのケースに対応する値を設定することができます。
この値のことをraw value(ローバリュー)と言います。

全てのrawValueの型は同じである必要があり、
指定できる型は、Int型、Double型、String型、Character型などになります。

rawValueの定義方法は、
クラスでスーパークラスを定義する時のように、
enum 列挙型名: rawValueの型 { }と定義します


enum 列挙型名: rawValueの型 {
   case ケース名1 = rawValue1
   case ケース名2 = rawValue2
}

Int型のrawValueを設定した列挙型は次のようになります。


enum Sample: Int {
    case a = 1
    case b = 2
    case c = 3
}

rawValueを定義されている列挙型では、
rawValueと列挙型の相互変換を行うためにの機能が暗黙的に追加されます。

追加される機能というのが、失敗可能イニシャライザinit(rawValue:)
プロパティrawValueの2つになります。

失敗可能イニシャライザは、rawValueと同じ型の値を引数にとります。

rawValueが一致するケースが存在すればそれに該当するケースを返し、
rawValueに一致するケースが存在しなければnilを返します。

また、ケースに対応するrawValueを取得するには、
変数名.rawValueで暗黙的に宣言されたrawValueプロパティにアクセスします。


enum Sample: Int {
    case a = 1
    case b = 2
    case c = 3
}

let a = Sample(rawValue: 1)   // a
let d = Sample(rawValue: 4)   // nil
print(a?.rawValue)

実行結果
Optional(1)

###rawValueのデフォルト値

Int型やString型のrawValueにはデフォルト値が存在し、
値を指定しない場合はデフォルト値が適用されます。

Int型のrawValueのデフォルト値は、
最初のケースが0でそれ以降はインクリメントされた値になります。

String型のrawValueのデフォルト値は、
ケース名をそのまま文字列にした値がデフォルト値になります。

case other = 100でrawValueを100にしているので、
それ以降のデフォルト値は100+1の値になります。


// Int型のrawValue
enum SampleInt: Int {
    case none
    case one
    case other = 100
    case otherPlusOne
}

SampleInt.none.rawValue   // 0
SampleInt.one.rawValue   // 1
SampleInt.other.rawValue   // 100
SampleInt.otherPlusOne.rawValue   // 101

// String型のrawValue
enum SampleString: String {
    case home
    case shcool
    case park
}

SampleString.home.rawValue   // home
SampleString.shcool.rawValue   // shcool
SampleString.park.rawValue   // park

##連想値

列挙型のインスタンスは、ケースの情報に加えて、
連想値という付加情報を付与することができます。

連想値に指定できる型に制限はありません。

色の代表的な数値表現にRGB(Red,Green,Blue)が存在します。

次のサンプルコードのように
表現方法(rgb)をケース、数値(rgbそれぞれの値)を連想値として表現すれば、
RGBを列挙型として表現することができます。

RGBのように連想値の型が全て同じ型でもいいですし、
ケースhumanのようにString, Int, Stringといった異なる型でも大丈夫です。

列挙型Associatedを定義し、その中に二つのケースを定義しました。

その後、定数colorと定数humanでインスタンス化し、
Associated型を格納する配列arrayに各インスタンスを格納しています。

for文で配列の中の値を順に取り出し、switch文で条件分岐しています。
この時に、列挙型から連想値を取り出すための手法として、
バリューバインディングパターンを使っています。


enum Associated {
    case rgb(Float, Float, Float)
    case human(String, Int, String)
}

let color = Associated.rgb(0.0, 0.33, 0.66)
let human = Associated.human("Saitou", 20, "釣り")

let array: [Associated] = [color, human]

for item in array {
    switch item {
    case .rgb(let r, let g, let b):
        print("Red: \(r), Green: \(g), Blue: \(b)")
    case .human(let name, let age, let hobby):
        print("名前: \(name), 年齢: \(age), 趣味: \(hobby)")
    }
}

実行結果
Red: 0.0, Green: 0.33, Blue: 0.66
名前: Saitou, 年齢: 20, 趣味: 釣り

バリューバインディングパターンとは、値を変数にや定数に代入する手法になります。
具体的にはcase .rgb(let r, let g, let b)の部分を指します。

どのような処理かというと、
let r = 第一引数、let g = 第二引数、let b = 第三引数 となります。
なので、case以降は、定数r, 定数g, 定数b を使用することができます。

##Caselterableプロトコル

列挙型を使用していると、全てのケースを配列として取得したい場合が出てきます。

例えばですが、都道府県を列挙型で表現する場合、
選択肢を表示するためには全てのケースの配列が必要になります。

CaseIterableプロトコルはその要件を満たすプロトコルになります。

CaseIterableプロトコルに準拠した列挙型には、
自動的にallCasesプロパティが追加されます。

このallCasesプロパティが全てのケースを返すプロパティになります。

次のサンプルコードでは、
列挙型Japanを定義しいくつか県を書いてみました。

その後、Japan.allCasesを実行したところ、
全てのケースを取得することができました。

allCasesプロパティは、enum Japan: CaseIterable { }の部分で
CaseIterableへの準拠を宣言したので、コンパイラによって自動的に作られております。


enum Japan: CaseIterable {
    case 群馬, 栃木, 東京, 神奈川
    case 青森, 岩手, 宮城, 秋田
}

Japan.allCases   // [群馬, 栃木, 東京, 神奈川, 青森, 岩手, 宮城, 秋田]

allCasesは、コンパイラによって自動生成された実装を使わずに、
自分でallCasesを実装することもできます。

###allCasesが自動生成されない条件
列挙型が連想値を持つ場合はallCasesが自動生成されなくなります。

つまり、連想値を持つ列挙型で全てのケースを列挙したい場合は、
プログラマがallCasesプロパティを自ら実装する必要があります。


enum Japan: CaseIterable {
    case 東京(String, String, String, String)
    case 神奈川(String, String, String, String)
    
    static var allCases: [Japan] {
        return [
            .東京("新宿", "渋谷", "中目黒", "江戸川"),
            .神奈川("横浜", "鎌倉", "小田原", "江ノ島")
        ]
    }
}

Japan.allCases   // {東京 "新宿", "渋谷", "中目黒", "江戸川"}, {神奈川 "横浜", "鎌倉", "小田原", "江ノ島"}

各要素にアクセスするには先程のバリューバインディングパターンを使ったりします。


enum Japan: CaseIterable {
    case 東京(String, String, String, String)
    case 神奈川(String, String, String, String)
    
    static var allCases: [Japan] {
        return [
            .東京("新宿", "渋谷", "中目黒", "江戸川"),
            .神奈川("横浜", "鎌倉", "小田原", "江ノ島")
        ]
    }
}

let japan = Japan.allCases

for item in japan {
    switch item {
    case .東京(let a, let b, let c, let d):
        print("東京:\(a),\(b),\(c),\(d)")
    case .神奈川(let a, let b, let c, let d):
        print("神奈川:\(a),\(b),\(c),\(d)")
    }
}

実行結果
東京新宿,渋谷,中目黒,江戸川
神奈川横浜,鎌倉,小田原,江ノ島

列挙型の説明については以上になります。

構造体、クラス、列挙型と3つの型について紹介しましたが、
どの場面ではこの型を使えばいい!というところが正直まだ理解できていません。

個人的には、すでに決まっている情報で型を定義する時は列挙型
それ以外は基本的に構造体を使い、
参照型や継承として持つ機能を使いたい時にはクラスを使用すればいいのかな?
という印象です。

これについては、もっと歴を重ねて一人前になったら再度共有したいと思います。

この記事の他にも型の種類について記載した記事がありますのでぜひご覧ください!

【Swift】型の種類〜基礎知識〜
【Swift】型の種類〜構造体〜
【Swift】型の種類〜クラス前編〜

最後までご覧いただきありがとうございました。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?