All Articles

SwiftのEnumの知られざる便利な使い方

はじめに

Swift 言語の凄さは色々あるけど、Enum は特に他の言語にはない使い方ができます。 その前に、オフィシャルで書かれているベーシックな機能のおさらいをしましょう。

オフィシャルページより引用しますが、

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

のように Enum に値をもたせることができます。しかも Case 毎に全く異なる型でも受け付けます。 自分が扱った他の言語の中ではこのように扱えるものはありません。非常に強力ですね。

let をつけることで値を取り出せます。

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).")
}

次に、Enum の中に Enum を持つことができます。

自分が担当した PJ で、エラーを区別するために Enum を使っていましたが、単純にエラーの種類毎に Case を書いていくと膨大なケースに分岐になってしまいます。また、いくつかの種類のエラーは一つにまとめたい、という状況だったので、Enum の中に Enum をもたせることで対応しました。

以下はオフィシャルの例となります。

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

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, 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))

Enum の知られざる機能

ここまでも Swift の Enum 凄さに十分驚いているのですが、これ以外にも 2 つ活用方法があります。

Enum を使って Protocol をグループ化

protocol OrderState {}
protocol Billable {}
protocol Cancellable {}

のような 3 つの Protocol があり、この 3 つの Protocol の機能をもたせた型を作るために

enum Ordered: OrderState, Cancellable, Billable {}

と書けます。 Enum は case を持っていません。こんなことも可能です。

この Enum は型として扱えるので、以下のように Generics に使えます。

final class Order<T: OrderState> {
    private let id: Customer.ID
    private var item: OrderedItem
    init(id: Customer.ID,
         item: OrderedItem) {
        self.id = id
        self.item = item
    }
}

extension Order where T == Ordered {
    convenience init(id: Customer.ID, cart: Cart) {
        self.init(id: id, item: OrderedItem(items: cart.items))
    }
}

Protocol なら、typealias を使って

typealias Ordered = OrderState & Cancellable & Billable

と書けばよいのではないか?て自分も疑問に思いました。typealias についてはこちらに記事があります。

今回使った例では、

let orderedItem = OrderedItem(items: [
    Item(name: "Ball"),
    Item(name: "Camera")
])

let r = Order<Ordered>(id: "1", item: orderedItem)

のようにイニシャライズするときには、typealias で書き換えたものを使うとエラーが発生します。

Protocol type 'Ordered' (aka 'Billable & Cancellable & OrderState') cannot conform to 'OrderState' because only concrete types can conform to protocols

つまり、typealias と Enum では生成するものが異なり、Enum の方は、concrete types が作られるということですね。

namespace として Enum を扱う

Swift では namespace として case なし enum が使える の記事を見て学びました。

static function としてグローバルに置きたい場合且つ、関数名を短くしたい場合などに morningGreeting() と書くよりも、 Morning.greeting() 方がスッキリ書けますね。  ※良い例が思いつかなかった。

enum Morning {
    static func greeting() {}
}
enum Afternoon {
    static func greeting() {}
}

また上の記事では、Class でも同様のことができるけど、イニシャライザさせたくない Static な Function だけを扱う際も有効だと書かれています。