swiftのenumでStringがカスタムなときでもenumを諦めない

# はじめに

アプリ内の状態をenumで表す時、それをサーバーに伝える際に文字列として表現するようなケースがあった時、その中にカスタムな文字列を使いたい場合にenumを諦めてないだろうか。

例えば次のような感じ。SampleEventはeventで列挙できるパターン3つしかないんだけど、文字列はカスタムしたいみたいな。

```swift
// case customのStringが変わる場合は使いづらい
enum SampleEvent: String {
    case event1 = "e1"
    case event2 = "e2"
    case custom = "このStringが変わるようにしたい"
}
```

enumと固定されたStringが1対1で対応しているならその時はそれでいいけど、カスタムな要素があるとenum :Stringはそのままでは使えない。なので、enum :stringは諦める。ただし、enumを使う事自体は諦めなくていいっていう話です。

その際のやり方は色々あるだろうけど、次の項目を知ってると無理なくできる

- `Associated Value` を使う
- `RawRepresentable` のプロトコルに沿ってrawValueを実装する

# 本題: カスタムな文字列があるenumを実装する

## `Associated Value` を使う

まず`Associated Value` を使う。そもそもswiftでenumを利用する際に`Associated Value`を知ってるから知らないかでだいぶ変わる。困ったら大抵 `Associated Value`が解決してくれる。

例えば次のようなenum AppEventNameで 3つのEventが有るとして、case customにカスタムな文字列をStringを使いたいので `Associated Value`を使う。

```swift
public enum AppEventName {
    
    // MARK: General
    case completedRegistration
    case completedTutorial

    // MARK: Custom
    case custom(String)
    
    public init(_ string: String) {
        self = .custom(string)
    }
}
```

2つの固定のcaseに続いて.customはenumを利用する側がカスタムなStringに使いたいというわけ。

ちなみに`init(_ string: String)` 使って初期化できるようにしておくと便利

```swift
let event = AppEventName.custom("お前が好きなカスタムなイベントをキメろ！")
```

↓ initがあるので

```swift
let event = AppEventName("お前が好きなカスタムなイベントをキメろ！")
```

## `RawRepresentable`のプロトコルに沿ってrawValueを実装する

次にカスタムなこの文字列を無理なく取り出したいじゃないですか。なので`RawRepresentable`のプロトコルに沿ってメソッドを実装する。

先述したenum `AppEventName` からStringの値を利用するならrawValueを使えるようにするのが自然に思えるので、protocol`RawRepresentable`に準拠してあげる。

```swift
extension AppEventName: RawRepresentable {
    public init?(rawValue: String) {
        self = .custom(rawValue)
    }
    
    /// The corresponding `String` value.
    public var rawValue: String {
        switch self {
        case .completedRegistration: return "登録完了"
        case .completedTutorial: return "チュートリアルオワタ"

        case .custom(let string): return string
        }
    }
}
```

`rawValue`で該当する文字列定数を返したりカスタムなStringを返す具体的な利用例は次

```swift
let event0 = AppEventName.completedRegistration
print(event0.rawValue)   // => 登録完了

let event0_1 = AppEventName.completedTutorial
print(event0_1.rawValue) // => チュートリアルオワタ

let event0_2 = AppEventName("お前が好きなカスタムなイベントをキメろ！")
print(event0_2.rawValue) // => お前が好きなカスタムなイベントをキメろ！
```

すごい、これで自然な感じがしてくる。

### switch文を書きたいならこんな感じ

もしswitchを使うとしてもstringはrawValueで取り出せるし、`case .custom(let string):`で取り出してもいい。

```swift
let event = AppEventName("お前が好きなカスタムなイベントをキメろ！")
switch event {
case .completedRegistration:
    // ...いろいろな処理...
    print(event.rawValue) 
case .completedTutorial:
    // ...いろいろな処理...
    print(event.rawValue) 
case .custom(let string):
    // ...いろいろな処理...
    print(string)         // => お前が好きなカスタムなイベントをキメろ！
}
```

## ここまでのまとめ

以上でだいたいやりたいことが出来た。

さて、こういったやり方は一体どっから知ったかというと、facebook-ios-sdkのswift版が2016年の夏に公開されていて、そこで使われていただけ。

https://github.com/facebook/facebook-sdk-swift/blob/0.2.0/Sources/Core/AppEvents/AppEventName.swift#L75-L77

もうだいぶ前に公開されていたため大概みんな知ってるかと思ってたんだけど、これを知らずにenumを使わない方法をやってたりするのを目にしたので、こういう文章にしておいた。

# その他

facebook-ios-sdkのAppEventNameには、その他の役に立つような立たないような実装もあるのでそれも書いておく。

## protocol `ExpressibleByStringLiteral`に準拠するように実装しておく

カスタムな文字列でswitchしたいなら、protocol `ExpressibleByStringLiteral`に準拠するように次の3つのメソッドを実装しておく

```swift
extension AppEventName: ExpressibleByStringLiteral {
    public init(stringLiteral value: StringLiteralType) {
        self = .custom(value)
    }
    
    public init(unicodeScalarLiteral value: String) {
        self.init(stringLiteral: value)
    }
    
    public init(extendedGraphemeClusterLiteral value: String) {
        self.init(stringLiteral: value)
    }
}
```

雑だけど利用例は次のようになる。

```swift
let event = AppEventName("お前が好きなカスタムなイベントをキメろ！")

switch event {
case "登録完了":
    break
case "チュートリアルオワタ":
    break
case "お前が好きなカスタムなイベントをキメろ！":
    print("このやり方が役に立つ日が来るのだろう") // => このやり方が役に立つ日が来るのだろう
default:
    break
}
```

## protocol `CustomStringConvertible`に準拠してdescriptionも実装

もう一つ、主に開発中にprintを実行したときにenumの値よりStringを表示して欲しいのでprotocol `CustomStringConvertible`に準拠してdescriptionも実装している

```swift
extension AppEventName: CustomStringConvertible {
    /// Textual representation of an app event name.
    public var description: String {
        return rawValue
    }
}
```

書くまでもないけど利用例としては次の通り

```swift
let event = AppEventName("お前が好きなカスタムなイベントをキメろ！")
print(event)                     // => お前が好きなカスタムなイベントをキメろ！
print(String(describing: event)) // => お前が好きなカスタムなイベントをキメろ！
```

これはお作法としてやっておくのはまあ良いとは思う。実際にアプリ内で処理に使うのはrawValueで充分だけど、CustomStringConvertibleを実装しておくと、`String(describing: event)`が使えるのでそれはまあ何かに使えるかもしれない。

# おわりに

enumは強い制約を与えるので仕様を見える化する際にそれが適切であればenumを使うのを諦めない方向で設計を考えてくれると、後でコードを修正する際に助かる。enumを使うケースで安易にStructや文字列定数を並べたものを利用してしまうと制約を与えられないため、複数のパターンを考慮しないといけなくなる。