#列挙型 とは
列挙方は値型の一種で、複数の識別子をまとめる型になります。
曜日を例にしますと、月火水木金土日の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】型の種類〜クラス前編〜
最後までご覧いただきありがとうございました。