Swift 言語ガイド – 8. 列挙型

Swift

はじめに

公式ページのSwift 言語ガイド「The Swift Programming Language Swift 5.3」に基づき記載いたします。本ページでは、「列挙型」について記載いたします。
記載内容に誤り等ございましたら、ご連絡をいただければ幸いです。

8. 列挙型

列挙型は、関連する値のグループに共通の型を定義し、コード内で型に安全な方法でそれらの値を操作できるようにします。

Cに精通している場合は、Cの列挙が関連する名前を整数値のセットに割り当てることをご存知でしょう。Swiftの列挙は、はるかに柔軟性があり、列挙の各ケースに値を提供する必要はありません。列挙型ごとに値(raw valueと呼ばれる)が提供されている場合、値は文字列、文字、または任意の整数型または浮動小数点型の値にすることができます。

あるいは、列挙型のケースは、ユニオンやバリアントが他の言語で行うのと同じように、それぞれの異なるケース値とともに格納される任意の型の関連する値(associated values)を指定できます。関連するケースの共通セットを1つの列挙型の一部として定義できます。各列挙型には、適切な型の異なる値のセットが関連付けられています。

Swiftの列挙型は、それ自体がファーストクラスの型です。これらは、列挙型の現在の値に関する追加情報を提供する計算プロパティや、列挙型が表す値に関連する機能を提供するインスタンスメソッドなど、従来はクラスでのみサポートされていた多くの機能を採用しています。列挙型は、初期ケース値を提供するイニシャライザーを定義することもできます。元の実装を超えて機能を拡張するように拡張できます。また、プロトコルに準拠して標準機能を提供できます。

これらの機能の詳細については、プロパティ、メソッド、初期化、拡張機能、およびプロトコルを参照してください。

8.1. 列挙型構文

enumキーワードを使用して列挙型を導入し、それらの定義全体を中括弧のペア内に配置します。

enum SomeEnumeration {
    // 列挙の定義はここにあります
}

コンパスの4つの主要なポイントの例を次に示します。

enum CompassPoint {
    case north
    case south
    case east
    case west
}

列挙型で定義された値(north, south, east, and westなど)は、その列挙型のケースです。 caseキーワードを使用して、新しい列挙型ケースを導入します。

注意
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など)は大文字で始まります。列挙型に複数の名前ではなく単一の名前を付けて、自明となるようにします。

var directionToHead = CompassPoint.west

directionToHeadの型は、CompassPointの可能な値の1つで初期化されたときに推測されます。directionToHeadがCompassPointとして宣言されたら、短いドット構文を使用して別のCompassPoint値に設定できます。

directionToHead = .east

directionToHeadの型はすでにわかっているため、値を設定するときに型を削除できます。これにより、明示的に型指定された列挙値を操作するときに、非常に読みやすいコードが作成されます。

8.2. 列挙型の値とSwitchステートメントの照合

個々の列挙型の値をswitchステートメントで照合できます。

directionToHead = .south
switch directionToHead {
case .north:
    print("Lots of planets have a north")
case .south:
    print("Watch out for penguins")
case .east:
    print("Where the sun rises")
case .west:
    print("Where the skies are blue")
}
// "Watch out for penguins"を出力します

このコードは次のように読むことができます。

「directionToHeadの値を検討してください。.northに等しい場合は、「Lots of planets have a north」と出力します。.southに等しい場合は、「Watch out for penguins」と出力してください。

…等々。

制御フローで説明されているように、列挙型のケースを検討するときは、switchステートメントを網羅する必要があります。.westのケースを省略した場合、CompassPointのcaseの完全なリストが考慮されないため、このコードはコンパイルされません。網羅性を要求することで、列挙型のケースが誤って省略されないようにします。

すべての列挙型のケースにcaseを提供することが適切でない場合は、明示的に対処されていないケースをカバーするdefaultのケースを提供できます。

let somePlanet = Planet.earth
switch somePlanet {
case .earth:
    print("Mostly harmless")
default:
    print("Not a safe place for humans")
}
// "Mostly harmless"を出力します

8.3. 列挙型ケースの反復

一部の列挙型では、その列挙型のすべてのケースのコレクションがあると便利です。これを有効にするには、列挙型の名前の後に:CaseIterableと記述します。Swiftは、すべてのケースのコレクションを列挙型のallCasesプロパティとして公開します。次に例を示します。

enum Beverage: CaseIterable {
    case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// "3 beverages available"を出力します

上記の例では、Beverage.allCasesを記述して、Beverage列挙型のすべてのケースを含むコレクションにアクセスします。allCasesは、他のコレクションと同じように使用できます。コレクションの要素は列挙型のインスタンスであるため、この場合はBeverageの値です。上記の例では、ケースの数をカウントし、以下の例では、forループを使用してすべてのケースを反復処理しています。

for beverage in Beverage.allCases {
    print(beverage)
}
// coffee
// tea
// juice

上記の例で使用されている構文は、CaseIterableプロトコルに準拠しているものとして列挙型をマークします。プロトコルについては、プロトコルを参照してください。

8.4. 関連する値(Associated Values)

前のセクションの例は、列挙型のケースがそれ自体で定義された(そして型付けされた)値である方法を示しています。定数または変数をPlanet.earthに設定し、後でこの値を確認できます。ただし、これらのケース値と一緒に他の型の値を格納できると便利な場合があります。この追加情報は関連する値(Associated Values)と呼ばれ、コードでそのケースを値として使用するたびに異なります。

Swiftの列挙型を定義して、任意の型の関連する値(Associated Values)を格納できます。値の型は、必要に応じて、列挙型のケースごとに異なる場合があります。これらに類似した列挙型は、他のプログラミング言語では、識別された共用体、タグ付き共用体、またはバリアントとして知られています。

たとえば、在庫追跡システムが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)
}

これは次のように読むことができます:

「バーコードと呼ばれる列挙型を定義します。これは、upcの値とそれに関連付けられた型の値(Int, Int, Int, Int)、またはqrCodeの値と関連付けられた型の値のいずれかを取ることができます。」

この定義は、実際のInt値またはString値を提供するものではなく、バーコード定数および変数がBarcode.upcまたはBarcode.qrCodeと等しい場合に格納できる関連する値(Associated Values)の型を定義するだけです。

次に、次のいずれかの型を使用して新しいバーコードを作成できます。

var productBarcode = Barcode.upc(8, 85909, 51226, 3)

この例では、productBarcodeという新しい変数を作成し、それに関連付けられたタプル値(8, 85909, 51226, 3)を持つBarcode.upcの値を割り当てます。

同じ製品に異なる型のバーコードを割り当てることができます。

productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

この時点で、元のBarcode.upcとその整数値は、新しいBarcode.qrCodeとその文字列値に置き換えられます。バーコード型の定数と変数は、.upcまたは.qrCodeのいずれかを(関連する値(Associated Values)とともに)格納できますが、一度に格納できるのはそのうちの1つだけです。

列挙型の値とSwitchステートメントの照合の例と同様に、switchステートメントを使用して、さまざまなバーコードタイプを確認できます。ただし、今回は、関連する値(Associated Values)がswitchステートメントの一部として抽出されます。switchケースの本体内で使用するために、各関連する値(Associated Values)を定数(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."を出力します

列挙型ケースにすべての関連する値(Associated Values)が定数として抽出される場合、または、すべてが変数として抽出される場合は、簡潔にするためにケース名の前に単一のvarまたはletの注釈を配置できます。

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."を出力します

8.5. Raw Values

関連する値(Associated Values)のバーコードの例は、列挙型のケースが、さまざまな型の関連する値(Associated Values)を格納することを宣言する方法を示しています。関連する値(Associated Values)の代わりに、列挙型のケースには、すべて同じ型のデフォルト値(Raw Valuesと呼ばれる)が事前に入力されている場合があります。

名前付き列挙型のケースと一緒にASCII値(Raw Values)を格納する例を次に示します。

enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

ここで、ASCIIControlCharacterと呼ばれる列挙型のRaw Valuesは、Character型であると定義されており、より一般的なASCII制御文字のいくつかに設定されています。文字値は、文字列と文字で説明されています。

Raw Valuesは、文字列、文字、または整数型または浮動小数点数型のいずれかです。各Raw Valuesは、その列挙型の宣言内で一意である必要があります。

注意
Raw Valuesは、関連する値(Associated Values)と同じではありません。上記の3つのASCIIコードのように、コードで列挙型を最初に定義するときに、Raw Valuesが事前入力された値に設定されます。特定の列挙型ケースのRaw Valuesは常に同じです。関連する値(Associated Values)は、列挙型のケースの1つに基づいて新しい定数または変数を作成するときに設定され、作成するたびに異なる可能性があります。

8.6. 暗黙的に割り当てられたRaw Values

整数または文字列のRaw Valuesを格納する列挙型を操作する場合、それぞれの場合にRaw Valuesを明示的に割り当てる必要はありません。割り当てない場合、Swiftが自動的に値を割り当てます。

たとえば、Raw Valuesに整数が使用されている場合、各ケースの暗黙の値は前のケースより1つ多くなります。 最初のケースに値が設定されていない場合、その値は0です。

以下の列挙型は、以前のPlanetの列挙型を改良したものであり、太陽からの各惑星の順序を表す整数のRaw Valuesが含まれています。

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}

上記の例では、Planet.mercuryの明示的なRaw Valuesは1であり、Planet.venusの暗黙的なRaw Valuesは2です。

文字列がRaw Valuesに使用される場合、各ケースの暗黙的な値は、そのケースの名前のテキストです。

以下の列挙型は、以前のCompassPoint列挙型を改良したものであり、各方向の名前を表す文字列のRaw Valuesが含まれています。

enum CompassPoint: String {
    case north, south, east, west
}

上記の例では、CompassPoint.southには「south」という暗黙のRaw Valuesがあります。

rawValueプロパティを使用して列挙ケースのRaw Valuesにアクセスします。

let earthsOrder = Planet.earth.rawValue
// earthsOrderは3です

let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirectionは"west"です

8.7. Raw Valueの初期化

Raw Values型で列挙型を定義すると、列挙型は、Raw Valuesの型の値を(rawValueと呼ばれるパラメータとして)取得し、列挙型のケースまたはnilを返す初期化子を自動的に受け取ります。この初期化子を使用して、列挙型の新しいインスタンスを作成することができます。

この例では、Raw Values7からuranusを識別します。

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanetはPlanet?型で、そしてPlanet.uranusに等しい

ただし、すべての可能なInt値が一致するPlanetを見つけるわけではありません。このため、Raw Valuesの初期化子は常にオプショナルの列挙型ケースを返します。上記の例では、possiblePlanetは、Planet?または「オプショナルのPlanet」型です。

注意
すべてのRaw Valuesが列挙型を返すわけではないため、Raw Valuesの初期化子は失敗可能な初期化子です。詳細については、FailableInitializersを参照してください。

位置が11のPlanetを見つけようとすると、Raw Valuesの初期化子によって返されるオプショナルのPlanetの値はnilになります。

let positionToFind = 11
if let somePlanet = Planet(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"を出力します

この例では、オプショナルバインディングを使用して、Raw Valuesが11のPlanetにアクセスしようとします。ステートメントif let somePlanet = Planet(rawValue: 11)は、オプショナルのPlanetを作成し取得できる場合は、somePlanetをそのオプショナルのPlanetの値に設定します。この場合、位置が11のPlanetを取得することはできないため、代わりにelseブランチが実行されます。

8.8. 再帰列挙型

再帰列挙型は、1つ以上の列挙型ケースに関連する値(Associated Values)として列挙型の別のインスタンスを持つ列挙型です。列挙型のケースが再帰的であることを示すには、その前にindirectを記述します。これにより、コンパイラに必要な間接レイヤーを挿入するように指示します。

たとえば、単純な算術式を格納する列挙型は次のとおりです。

enum ArithmeticExpression {
case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

列挙型の開始前にindirectを記述して、関連する値(Associated Values)のすべての列挙型のケースのindirectを有効にすることもできます。

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

この列挙型には、単純な数値、2つの式の加算、および2つの式の乗算の3種類の算術式を格納できます。加算と乗算の場合には、算術式でもある値が関連付けられています。これらの関連する値(Associated Values)により、式をネストできます。たとえば、式(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"を出力します

この関数は、関連する値(Associated Values)を返すだけでプレーンな数値を評価します。左側の式を評価し、右側の式を評価してから、それらを加算または乗算することにより、加算または乗算を評価します。

タイトルとURLをコピーしました