はじめに
Webエンジニアです。最近Swiftに入門しました。公式のSwiftツアーを消化したのですが、Enumの理解が曖昧なので深掘りしました。
読んだのは、Enumに関する公式の解説です。
写経
import UIKit
/*
Enumのシンタックス
*/
// Enumの宣言
enum SomeEnumeration {
// ...コード
}
// 先頭の1文字は大文字。
// Enumで要素の宣言
enum CompassPoint {
case North
case South
case East
case West
}
// Enumの要素はコンマ区切りでの宣言も可能
enum Planet {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
// enumを定義すると、新しい型が定義されたことになります。
// 複数形ではなく、単数形で命名します。
var directionToHead = CompassPoint.West // West
// directionToHeadの型は、CompassPointのどれかの要素で初期化したときに推論されます。
var directionToRight: CompassPoint
directionToRight = .East // East
// 変数の型がEnum型で宣言されているなら、代入時にはEnum名は省略可能です。
/*
Switch文でEnumの値をマッチングさせる
*/
// switch文を使って、Enumの値とマッチさせることができる。
directionToHead = .South
switch directionToHead {
case .North:
print("たくさんの植物があります")
case .South:
print("ペンギンを見ましょう")
case .East:
print("太陽が昇る方角です")
case .West:
print("空が青い方角です")
} // ペンギンを見ましょう
// caseはCompassPointのすべてのcaseを網羅している必要があります。
// default文を使えば、caseを省略することができます。
let somePlanet = Planet.Earth
switch somePlanet {
case .Earth:
print("もっとも無害です")
default:
print("人間にとって安全ではありません")
} // もっとも無害です
/*
associated values(関連する値)
*/
// Enumのそれぞれcaseに関連する値を持たせることができます。
// 以下、1次元バーコードと2次元バーコードの例です。1次元バーコードは4つの数値で、2次元バーコードは1つの文字列で表すことができます。
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
}
// 上記は、Barcode型のEnumを定義しています。UPCAは(Int, Int, Int, Int)という関連した値を持ち、QRCodeは(String)という関連した値を持ちます。
// 定義時には実際のIntあるいはStringの値は代入しません。関連する値の型だけを宣言しています。
// 以下のように、宣言時にassociated valuesに実際の値を代入します。
var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)
// 以下のような再代入は可能です。ただし、元のUPCAの値はなくなってしまいます。
productBarcode = Barcode.QRCode("ABCDEFGHIJKLMNOP")
// 先述のように、異なるバーコードの型をswitch文で分岐処理することができます。
// ただし、異なる点は、caseのassociated valuesを抽出できます。
switch productBarcode {
case .UPCA(let numberSystem, let manufacturer, let product, let check):
print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case .QRCode(let productCode):
print("QR code: \(productCode).")
} // QR code: ABCDEFGHIJKLMNOP.
// もし全てのassociated valuesを抽出するならば、以下のようにも書けます。
switch productBarcode {
case let .UPCA(numberSystem, manufacturer, product, check):
print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .QRCode(productCode):
print("QR code: \(productCode).")
} // QR code: ABCDEFGHIJKLMNOP.
/*
raw value(直接の値)
*/
// associated valueと異なり、case自体に値を持たせることができます。それがrawValueであり、rawValueはすべて同じ型でなければいけません。
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
// ASCIIControlCharacterはCharacter型のrawValueを持ちます。
// ただし、rawValueはEnumの中でユニークでなければいけません。例:case CarriageReturn = "\n"はエラー。
// associated valueがenumの利用時に代入されるのに対して、rawValueはenumの宣言時に代入されます。つまりrawValue
// raw valueを暗黙的に割り当てることもできます。
enum Planet2: Int {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
// 上の例では、それぞれのcaseに0から始まるrawValueが割り当てられています。
let venus = Planet2.Venus.rawValue // 1
// また、以下の例では、rawValueが1から始まります。
enum Planet3: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
let venus2 = Planet3.Venus.rawValue // 2
// もしrawValueの型をStringにした場合、case名がrawValueとなります。
enum CompassPoint2: String {
case North, South, East, West
}
let east = CompassPoint2.East.rawValue // East
// EnumにrawValueの型を指定すると、そのEnumは自動的にイニシャライザというものを持つようになります。
// イニシャライザが何をするかというと、rawValueを渡すと対応するcaseかnilを返してくれます。
let possiblePlanet = Planet2(rawValue: 6) // Uranus
// rawValueを使ったイニシャライザはオプショナルなEnumのcaseを返します。possiblePlanetの型はPlanet?
let nilPlanet = Planet2(rawValue: 50) // nil
// 50に対応するcaseはありません。nilを返します。
// 以下、もっと丁寧な例。
let positionToFind = 11
if let somePlanet = Planet2(rawValue: positionToFind) {
switch somePlanet {
case .Earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
} else {
print("There isn't a planet at position \(positionToFind)")
}
// There isn't a planet at position 11
/*
Recursive Enumerations(再帰的なEnum)
*/
// 再帰的なEnumとは、associated valueにEnumの他のインスタンスを持つEnumです。caseの前にindirectと書きます。
enum ArithmetricExpression {
case Number(Int)
indirect case Addition(ArithmetricExpression, ArithmetricExpression)
indirect case Multiplication(ArithmetricExpression, ArithmetricExpression)
}
// 以下のように、enumの先頭にindirectをつけることもできます。
indirect enum ArithmeticExpression {
case Number(Int)
case Addition(ArithmeticExpression, ArithmeticExpression)
case Multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 上記のcaseはそれぞれ、値そのまま、足し算、掛け算、を表します。
// 再帰的にすることによって、(5 + 4) * 2 みたいな使い方ができます。
// 以下、数式の部品を定義します。
let five = ArithmetricExpression.Number(5)
let four = ArithmetricExpression.Number(4)
let sum = ArithmetricExpression.Addition(five, four)
let product = ArithmetricExpression.Multiplication(sum, ArithmetricExpression.Number(2))
// 以下、数式の実行部分を定義します。
func evaluate(expression: ArithmetricExpression) -> Int {
switch expression {
case let .Number(value):
return value
case let .Addition(left, right):
return evaluate(left) + evaluate(right)
case let .Multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(product)) // 18
// https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145
メモと所感
- さすが公式解説だけあって、歯ごたえがありつつも分かりやすかった。
- 一度精読すればだいたいは理解できると思う。
- Enum自体に型があり、caseにも型があり、associatedValueにも型がある(?)
- 型初心者には辛い。。
- rawValueはcase自体の値で、associatedValueはcaseに紐づく値。
- rawValueはすべてのEnumで共通で、associatedValueはインスタンス毎に異なる。
- クラス変数とインスタンス変数みたいですね。
参考資料
理解の助けになりました。
→Swiftの列挙型 | Associated ValuesとRaw Values
以上です。何か誤りがありましたらコメントでお教えいただけると助かります。