enum Associated Values と Raw Values を両立させる

## 背景

Swift の enum においては、 Associated Values と Raw Values を利用することができます。

これらはそれぞれ単独での利用は問題ないのですが、両方を同時に利用するとすると、コンパイルエラーとなってしまいます。

**それぞれ単独での利用は OK**

```swift:Associated&#x20;Values
enum Screen {
  case home(tabIndex: Int)
  case search
  case setting
}
```

```swift:Raw&#x20;Values
enum Screen: String {
  case home = "Home"
  case search = "Search"
  case setting = "Setting"
}
```

**同時利用が NG**

```swift:Associated&#x20;Values&#x20;&&#x20;Raw&#x20;Values
enum Screen: String {
  case home(tabIndex: Int)  // コンパイルエラー "Enum with raw type cannot have cases with arguments"
  case search
  case setting
}
```

コンパイルエラーを回避して、 Associated Values と Raw Values を両立させようというのが今回の内容です。

ユースケースとしては、次のように iOS アプリの画面を enum で表現することを想定します。

- ホーム/検索/設定の3画面をタブ（ `UITabbar` を利用した下タブ）で構成
- ホーム画面は可変個のタブストリップ（上タブ）で子画面を保持
  - **Associated Values を利用** 
- スクリーンビュー測定のため、 enum 毎に特定の文字列を定義
  - **Raw Valuesを利用**

## どうするのか

Associated Values はそのまま。
Raw Values を `protocol RawRepresentable` に適合させることで実現します。

>For any enumeration with a string, integer, or floating-point raw type, the Swift compiler automatically adds RawRepresentable conformance. When defining your own custom enumeration, you give it a raw type by specifying the raw type as the first item in the enumeration’s type inheritance list. You can also use literals to specify values for one or more cases.

https://developer.apple.com/documentation/swift/rawrepresentable

とあるように、 Raw Values 自体は Swift コンパイラが `protocol RawRepresentable` に自動的に適合してくれた結果なので、これを自分で実装することとなります。

具体的には、以下のイニシャライザとプロパティ実装が要求されます。
（ `associatedtype RawValue` については、型推論が効くため省略可能です）

```swift
init?(rawValue: Self.RawValue)
var rawValue: Self.RawValue
```

上述の想定ユースケースにおける `enum Screen` を例にした実装だと、次の通りです。

```swift
enum Screen: RawRepresentable {
    case home(tabIndex: Int)
    case search
    case setting

    init?(rawValue: String) {
        switch rawValue {
        case Screen.search.rawValue:
            self = .search
        case Screen.setting.rawValue:
            self = .setting
        default:
            if let regex = try? NSRegularExpression(pattern: "^Home_Tab(.+)$"),
               let match = regex.firstMatch(in: rawValue, range: NSRange(location: 0, length: rawValue.count)),
               let range = Range(match.range(at: 1), in: rawValue),
               let tabIndex = Int(rawValue[range]) {
                // `case home(tabIndex: Int)` の rawValue フォーマットに合致
                self = .home(tabIndex: tabIndex)
            } else {
                return nil
            }
        }
    }

    var rawValue: String {
        switch self {
        case .home(let tabIndex):
            return "Home_Tab\(tabIndex)"
        case .search:
            return "Search"
        case .setting:
            return "Setting"
        }
    }
}
```

## メリット・デメリット

メリットとしては、 Associated Values と Raw Values を両立可能という結果そのものがあります。

もう少し具体的な例だと、 Raw Values 実装に依存した既存コードが大量にあるプロジェクトにおいて、 enum に Associated Values を利用した case を追加する必要性が発生した場合に、影響を最小限に抑えることが可能となってきます。

デメリットとしては、

- `init?(rawValue: Self.RawValue)`の実装が煩雑になる
- enum の表現力 / 記述容易性が失われる

といったことが挙げられます。

特に後者において、通常の enum 実装では、列挙子と rawValue の一意性を少ない記述で簡潔に表現でき、コンパイラの静的解析による保証もついてくる

```swift
enum Screen: String {
  case home = "Home"
  case search = "Search"
  case setting = "Search"   // コンパイルエラー "Raw value for enum case is not unique"
}
```

のですが、自分で `protocol RawRepresentable` への適合を実装するとなると、このあたりの恩恵が失われてしまいます。