列挙型(Enumerations)
最終更新日: 2023/9/17
原文: https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html
可能な値のリストを定義する独自の型をモデル化する。
列挙型は、関連する値のグループに共通の型を定義し、コード内で型安全にそれらの値を操作できるようにします。
C 言語に馴染みがあれば、C 言語の列挙型が、関連する名前のグループを整数値のセットに割り当てることを知っていると思います。Swift の列挙型は、それよりもはるかに柔軟性があり、列挙型の各ケースに値を割り当てる必要はありません。列挙ケースごとに(Raw Value と呼ばれる)値が与えられている場合、値は文字列、文字、または任意の整数型または浮動小数点型にすることができます。
あるいは、他の言語の unions
や variants
のように、それぞれのケースで任意の型の関連値を格納できます。それぞれで適切な型の異なる値のセットで関連付けられたケースを列挙型の一部として定義できます。
Swift の列挙型は、それ自体が第一級の型です。これらは、列挙型の現在値の追加情報を提供する計算プロパティや、列挙型が表す値に関わる機能を提供するインスタンスメソッドなど、従来はクラスでのみサポートされていた多くの機能を使うことができます。列挙型は、初期のケース値を指定するイニシャライザも定義しています。extension
で機能を拡張することもできます。また、プロトコルに準拠して標準機能を提供することもできます。
これらの機能の詳細については、Properties、Methods、Initialization(イニシャライザ)、Extensions(拡張)およびProtocols(プロトコル)を参照ください。
列挙型構文(Enumeration Syntax)
enum
キーワードを使用して列挙型を導入し、それらの定義全体を中括弧のペア({}
)内に配置します。
enum SomeEnumeration {
// 列挙型の定義をここに記載します
}
コンパスの 4 つの主要な方向の例を次に示します:
enum CompassPoint {
case north
case south
case east
case west
}
列挙型で定義された値(north
、south
、east
、west
など)は、その列挙ケースです。case
キーワードを使用して、新しい列挙ケースを導入します。
NOTE
C 言語や Objective-C などの言語とは異なり、Swift 列挙型のケースにはデフォルトで整数値が設定されていません。上記のCompassPoint
の例では、north
、south
、east
、west
は暗黙的に0
、1
、2
、3
になりません。代わりに、異なる列挙ケースはそれ自体が値であり、明示的に定義されたCompassPoint
型です。
複数のケースをカンマ(,
)で区切って 1 行で表示できます。
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
各列挙型の定義は、新しい型を定義します。Swift の他の型と同様に、それらの名前(CompassPoint
や Planet
など)は大文字で始まります。列挙型は、1 つのグループなことが自明なため、複数の名前ではなく単数の名前を付けましょう:
var directionToHead = CompassPoint.west
directionToHead
の型は、CompassPoint
のある値の 1 つで初期化された場合、型が推論されます。一度 directionToHead
が CompassPoint
として宣言されたら、次からは短いドット構文(.
)を使用して別の CompassPoint
値を設定できます。
directionToHead = .east
directionToHead
の型はすでにわかっているため、値を設定するときに型を省略できます。これにより、明示的に型指定された列挙型の値を操作するときに、非常に読みやすいコードが作成できます。
switch 文を使った列挙値のパターンマッチング(Matching Enumeration Values with a Switch Statement)
switch
文を使って、個々の列挙値をパターンマッチングできます。
directionToHead = .south
switch directionToHead {
case .north:
print("多くの惑星には北があります")
case .south:
print("ペンギンに気をつけて")
case .east:
print("太陽が登る場所")
case .west:
print("空が青い場所")
}
// ペンギンに気をつけて
このコードは次のように読むことができます。
「directionToHead
の値を検証してください。.north
に等しい場合は、「多くの惑星には北があります」と出力します。.south
に等しい場合は、「ペンギンに気をつけて」と出力してください」
…などなど。
Control Flow(制御フロー)で説明されているように、列挙ケースを検証するときは、switch
文で全てのケースを網羅する必要があります。.west
の case
を省略した場合、このコードはコンパイルできません。これによって、列挙ケースが誤って省略できないようにします。
全ての列挙ケースの case
を並べることが適切でない場合は、明示的に対処されていないケースをカバーする default
のケースを提供できます。
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("ほとんど無害")
default:
print("人間にとっては安全な場所ではありません")
}
// ほとんど無害
列挙ケースの繰り返し処理(Iterating over Enumeration Cases)
一部の列挙型では、その列挙型の全てのケースのコレクションがあると便利です。これを有効にするには、列挙型の名前の後に :CaseIterable
を記述します。Swift は、全てのケースのコレクションを列挙型の allCases
プロパティとして提供しています。次に例を示します:
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// 3 種類の飲み物を用意しています
上記の例では、Beverage.allCases
を記述して、列挙型 Beverage
の全てのケースを含むコレクションにアクセスします。allCases
は、他のコレクションと同じように使用できます。コレクションの要素は列挙型のインスタンスで、今回は Beverage
の値です。上記の例では、ケースの数をカウントし、下記の例では、for
ループを使用して全てのケースを繰り返し処理しています。
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice
上記の例で使用されている構文では、CaseIterable
プロトコルに準拠しています。プロトコルの詳細については、Protocols(プロトコル)を参照ください。
関連値(Associated Values)
前のセクションの例は、列挙ケースがそれ自体を定義された(および型指定された)値にする方法を示しています。定数または変数を Planet.earth
に設定し、後でこの値を確認できます。ただし、これらのケース値と一緒に他の型の値を保持できると便利な場合があります。この追加情報は関連値と呼ばれ、コードでそのケースの値を使用する度に異なります。
Swift の列挙型を定義して、任意の型を関連値に格納できます。また、必要に応じて、列挙ケースごとに値の型を変えることができます。これらに類似した列挙型は、他のプログラミング言語では、discriminated unions
、tagged unions
、または variants
として知られています。
例えば、在庫追跡システムが 2 つの異なる型のバーコードで製品を追跡する必要があるとします。一部の製品には、0〜9
の数字を使用する UPC 形式の 1D バーコードでラベルが付けられています。各バーコードには番号システムの数字があり、その後に 5 つのメーカーコード数字と 5 つの製品コード数字が続きます。これらの後にチェックディジットが続き、コードが正しくスキャンされたことを確認します。
その他の製品は、QR コード形式の 2D バーコードでラベル付けされています。これは、ISO 8859-1 文字を使用でき、最大 2,953 文字の文字列をエンコードできます。
在庫追跡システムでは、UPC バーコードを 4 つの整数のタプルとして保存し、QR コードバーコードを任意の長さの文字列として保存すると便利です。
Swift では、いずれかの型の製品バーコードを定義するための列挙型は次のようになります。
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
これは次のように読むことができます:
「Barcode
と呼ばれる列挙型を定義します。これは、upc
の値と (Int、Int、Int、Int)
型の関連値、または qrCode
の値と String
型の関連値、のいずれかを取ることができます」
この定義は、実際の Int
値または String
値を提供するものではなく、Barcode.upc
または Barcode.qrCode
と等しい場合に Barcode
の定数および変数が格納できる関連値の型を定義しているだけです。
次に、いずれかの型を使用して新しい Barcode
を作成します。
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
この例では、関連付けられたタプル値 (8,85909, 51226, 3)
を持つ Barcode.upc
の値を代入して、productBarcode
という新しい変数を作成します。
同じ製品に異なる型のバーコードを代入することもできます。
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
この時点で、元の Barcode.upc
とその整数値は、新しい Barcode.qrCode
とその文字列値に置き換えられます。Barcode
の定数と変数は、.upc
または .qrCode
のいずれかを(関連値とともに)格納できますが、一度に格納できるのはそのうちの 1 つだけです。
Matching Enumeration Values with a Switch Statement(switch 文を使った列挙値のパターンマッチング)の例と同様に、switch
文を使用して様々なバーコード型を確認できます。ただし、今回は、関連値が switch
文の一部として抽出されます。switch
のケースの本文内で使用するために、関連値を定数(let
プレフィックス)または変数(var
プレフィックス)として抽出します。
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
// QR code: ABCDEFGHIJKLMNOP.
列挙ケースの全ての関連値が定数として抽出される場合、または全てが変数として抽出される場合は、簡潔にするために、ケース名の前に 1 つの let
または var
を付けるだけで問題ありません:
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// QR code: ABCDEFGHIJKLMNOP.
Raw Values
Associated Values(関連値)のバーコードの例は、列挙ケースが、様々な型に関連値を格納して宣言する方法を示しています。関連値の代わりに、列挙型には、全て同じ型のデフォルト値(Raw Valuesと呼ばれる)を事前に定義することもできます。
名前付きの列挙ケースと一緒に ASCII 値を格納する例を次に示します。
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
ここで、ASCIIControlCharacter
と呼ばれる列挙型の Raw Value は、Character
型で定義されており、一般的な ASCII 制御文字のいくつかが設定されています。Character
値は、Strings and Characters(文字列と文字)で説明しています。
Raw Value は、文字列、文字、または整数型または浮動小数点数型のいずれかです。各 Raw Value は、その列挙型内で一意でなければなりません。
NOTE
Raw Valueは、関連値と同じではありません。上記の3つの ASCII コードのように、コードで列挙型を最初に定義するときに、Raw Value は事前入力された値に設定されます。特定の列挙ケースの Raw Value は常に同じです。関連値は、列挙ケースに基づいて新しい定数または変数を作成するときに設定され、作成する度に異なる可能性があります。
暗黙的に割り当てられたRaw Value(Implicitly Assigned Raw Values)
整数または文字列の Raw Value を格納する列挙型を操作する場合、それぞれのケースに Raw Value を明示的に割り当てる必要はありません。代わりに Swift が自動的に値を割り当てます。
例えば、Raw Value に整数が使用されている場合、各ケースの暗黙の値は前のケースより 1 つ増えた値になります。最初のケースに値が設定されていない場合、その値は 0
です。
以下の列挙型は、以前の Planet
の列挙型を改良したもので、太陽からの各惑星の順序を表す整数の Raw Value が含まれています。
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
上記の例では、Planet.mercury
の明示的な Raw Value は 1
で、Planet.venus
の暗黙的な Raw Value は 2
です。
文字列が Raw Value に使用される場合、各ケースの暗黙的な値は、そのケースの名前のテキストです。
下記の列挙型は、以前の CompassPoint
列挙型を改良したもので、各方向の名前を表す文字列の Raw Value が含まれています。
enum CompassPoint: String {
case north, south, east, west
}
上記の例では、CompassPoint.south
には "south"
という暗黙の Raw Value があります。
rawValue
プロパティを使用して列挙ケースの Raw Value にアクセスします。
let earthsOrder = Planet.earth.rawValue
// earthsOrder は 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection は "west"
Raw Valueからの初期化(Initializing from a Raw Value)
Raw Value 型で列挙型を定義すると、列挙型は、Raw Value の型の値を(rawValue
と呼ばれるパラメータとして)受け取り、列挙型または nil
のいずれかを返すイニシャライザを自動的に提供します。このイニシャライザを使用して、列挙型の新しいインスタンスを作成することができます。
この例では、Raw Value7
から天王星を識別します。
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet の型は Planet? で Planet.uranus と等しい
ただし、全ての Int
値が惑星に一致するわけではありません。このため、Raw Value のイニシャライザは常にオプショナルの列挙型を返します。上記の例では、possiblePlanet
は Planet?または オプショナルの
Planet`型です。
NOTE
全ての Raw Value が列挙型を返すわけではないため、Raw Valueイニシャライザはnil
を返す可能性があります。詳細については、Failable Initializers(失敗可能イニシャライザ)を参照ください。
位置が 11
の惑星を見つけようとすると、Raw Value のイニシャライザによって返されるオプショナルの Planet
は nil
になります。
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
switch somePlanet {
case .earth:
print("ほとんど無害")
default:
print("人間にとっては安全な場所ではありません")
}
} else {
print("位置が \(positionToFind) の惑星はありません")
}
// 位置が 11 の惑星はありません
この例では、オプションバインディングを使用して、Raw Value が 11
の惑星にアクセスしようとします。if let somePlanet = Planet(rawValue: 11)
は、オプショナルの Planet
を作成し、取得できる場合は、somePlanet
をそのオプショナルの Planet
の値に設定します。この場合、位置が 11
の惑星を取得することはできないため、代わりに else
の分岐が実行されます。
再帰的列挙型(Recursive Enumerations)
再帰的列挙型は、1 つ以上の列挙ケースの関連値としてその列挙型の別のインスタンスを持つ列挙型です。列挙型が再帰的だということを示すには、その前に indirect
を記述します。これにより、コンパイラに indirect
のネストがあることを伝えることができます。
例えば、シンプルな算術式を格納する列挙型は次のとおりです。
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
列挙型の開始前に indirect
を記述して、関連値を持つ全てのケースを indirect
にすることもできます。
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
この列挙型には、シンプルな数値、2 つの式の加算、および乗算の 3 種類の算術式を格納できます。addition
と multiplication
のケースには、同じく算術式を関連値として格納しています。これらの関連値により、式をネストできます。例えば、式 (5 + 4) * 2
には、乗算の右側に数値があり、乗算の左側に別の式があります。データがネストしているため、データを格納する列挙ケースもネストをサポートする必要があります。つまり、再帰的でなければなりません。下記のコードは、(5 + 4) * 2
に対する ArithmeticExpression
の再帰的列挙を作成する方法を示しています。
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
再帰関数は、再帰構造を持つデータを操作する簡単な方法です。例えば、算術式を評価する関数は次のとおりです。
func evaluate(_ expression: ArithmeticExpression) -> 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
この関数は、関連値を返すだけで単純な数値を評価します。左側の式を評価し、右側の式を評価してから、それらをさらに加算または乗算することにより、addition
または multiplication
を評価します。