値オブジェクトへのCodable対応 <補足編> #iosdc

先日開催された[iOSDC Japan 2019](https://iosdc.jp/2019/)のルーキーズLT枠で[値オブジェクトのCodable対応](https://fortee.jp/iosdc-japan-2019/proposal/3b8c98a2-4861-449e-a2c9-e1327db431e2)という発表をさせて頂きました。

こちらに関して @takasek さんより、以下のようなご指摘・別解の共有を頂いたので、そちらについて記載させて頂きます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">すみません、いくつか気になったことが！<br>protocol ValueObjectを作ると、本来のDDD的なValue Object（familyNameとgivenNameを合わせて型表現するなど）の意味が歪んでしまうのを懸念しています<br>また、protocol ValueObjectでやりたいことはRawRepresentableで実現可能に見えますがどうでしょうか？</p>&mdash; takasek (@takasek) <a href="https://twitter.com/takasek/status/1171301577325330432?ref_src=twsrc%5Etfw">September 10, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## ValueObjectプロトコルという命名について

今回は以下の観点から`ValueObject`という名前をつけました。

- 値オブジェクトを利用して不適切な利用を防ぎたい
- `singleValueContainer`を利用した実装を共通化したい

しかしながら、ご指摘の通り複数のパラメータを保持するようなケース（例えば位置を表す緯度と経度を保持するような値オブジェクト）では適用できません。
今回はユーザーIDや写真IDといった`一意な識別子`を表す`Identifiable`が適切だったかと思います。

また、一意な識別子という事で`Dictionary`のキーに使う事も想定されるので、`Equatable`より`Hashable`が適切かと思いましたので、以下のように変更してみました。


```swift:Identifiable.swift

protocol Identifiable: Codable, CustomStringConvertible, Hashable {
  associatedtype Value: Codable, CustomStringConvertible, Hashable
  
  var value: Value { get }
  
  init(value: Value)
}

extension Identifiable {
  init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    let value = try container.decode(Value.self)
    self.init(value: value)
  }
  
  func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    try container.encode(value)
  }
  
  var description: String {
    return value.description
  }
}
```

## RawRepresentableを用いた別解法

教えて頂いたやり方を早速検証してみようと思ったところ、 @Mt-Hodaka さんが [CodableでのValueObject利用](https://qiita.com/Mt-Hodaka/items/5e11dcbfb04040fa685f) というタイトルで、`RawRepresentable`を用いた実装方法をまとめて頂いておりました。

こちらも参考にし、先程の例のように`Identifiable`プロトコルを作ってみたいと思います。
まず、`Codable, CustomStringConvertible, Hashable`のプロトコルを適用していきます。
[RawRepresentableのオフィシャルレファレンス](https://developer.apple.com/documentation/swift/rawrepresentable)に記載の通り、自分自身(`Self`)と`RawValue`が`Encodable, Decodable, Hashable`プロトコルに適合していると、それぞれのプロトコルのデフォルト実装がされるようです。
一方で`CustomStringConvertible`プロトコルは`RawRepresentable`でデフォルト実装はされていないので、`Identifiable`プロトコルを作成する場合には、デフォルト実装が必要になります。

上記を元に新しく作成した`Identifiable`プロトコルがこちらです。

```swift:Identifiable.swift
protocol Identifiable: RawRepresentable, Codable, CustomStringConvertible, Hashable {}

extension Identifiable where Self.RawValue : CustomStringConvertible  {
  var description: String {
    return rawValue.description
  }
}
```

### サンプルコードに適用

```swift
struct UserID: Identifiable {
  let rawValue: Int
}

struct User: Codable {
  let id: UserID
  let name: String
  let age: Int
}

let json = """
{
  "id" : 1234,
  "name" : "maguhiro",
  "age" : 34
}
""".data(using: .utf8)!
let user = try! JSONDecoder().decode(User.self, from: json)
print("\(user.id)") // 1234

let encodeJson = try! JSONEncoder().encode(user)
print(String(bytes: encodeJson, encoding: .utf8)!) // {"id":1234,"name":"maguhiro","age":34}

let a = UserID(rawValue: 1)
let b = UserID(rawValue: 2)
let c = UserID(rawValue: 1)

print(a == b) // false
print(a == c) // true

var map = [UserID: Int]()
map[a] = 2
map[b] = 10
map[c] = 100
print(map) // [2: 10, 1: 100]
```

自分の発表資料に記載したコードより、シンプルになりましたね！

## 謝辞

ご指摘頂いた事で考慮が足りていなかった部分を理解することができ、また、自分が知らなかった解法も学ぶことができました。
@takasek さん、 @Mt-Hodaka さんありがとうございました！！

また、今回iOSDC Japan 2019にて登壇する機会を提供頂きありがとうございました。
スタッフの皆様、参加者の皆様ありがとうございました！！