今すぐ extension Identifiable where Self: Hashable をやめろ！

TL;DR：

正確にはこんなコードをやめましょうね：

```swift
extension Identifiable where Self: Hashable {
    var id: Self { self }
}
```

---

まずこのようなコードを見てみましょう：

```swift:Identifiable
extension Identifiable where Self: Hashable {
    var id: Self { self }
}
```

```swift:Post.swift
struct Post: Hashable, Identifiable {
    let title: String
    let body: String
    var like: Bool
}
```

```swift:View.swift
struct PostContentView: View {
    @Binding var post: Post
    var body: some View {
        // ...
        Button {
            post.like.toggle()
        } label: {
            // ...
        }
    }
}

struct PostListView: View {
    @State private var posts: [Post] = //...
    var body: some View {
        NavigationView {
            List {
                ForEach($posts) { $post in
                    NavigationLink {
                        PostContentView(post: $post)
                    } label: {
                        // ...
                    }
                }
            }
        }
    }
}
```

ふむふむ、我ながらなかなかいいコード書いたじゃないか。早速動作確認してみよう：

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/53c82253-7899-5944-ab3f-a1690ce24853.gif)

！？？？いいね!ボタン押すたびに、なぜかリストに戻ってしまうんですが！？？？

そう、元凶はまさにタイトルに書いた通り、`extension Identifiable where Self: Hashable` のところのコードが悪さをしています。

なぜこのようなことが発生しているかというと、SwiftUI は値型駆動のフレームワークです。参照型と大きく違うのは、データの代入するたびに新しいインスタンスが生成されるので、同一オブジェクトの比較をインスタンス比較でできなくなるのです。そのため SwiftUI は `Identifiable` に大きく依存しているわけです：同一オブジェクトかどうかは、`Identifiable` の適合結果を見て判断するしかないのです！

このポイントを押さえておかないと、「あー `Identifiable` の適合って面倒臭い…そうだ！`Hashable` があってあれって自動適合じゃん？`Identifiable` の `ID` もそもそも `Hashable` に適合してればいいから、この型自体を `Hashable` に適合してしまえば、`Identifiable` なんてすぐにできちゃっていちいち自分で `var id: Xxx` とか作らなくて済むじゃん！」って発想に至るのもわかります。何せエンジニアはすぐ手を抜きたくなりますもんね。

しかし `Identifiable` プロトコルには、ちゃんとした存在意義があるのです。それは前述通り SwiftUI にオブジェクトの同一判定をさせるために必要だからです。なので残念ながら、その同一判定のロジックは、他の誰でもない、仕様を知ってる我々エンジニアが責任を持って書かなければならないのです。だから `Identifiable` に自動適合がないのです。

と言うわけで、今回は `title` が同じものであれば同じ記事とみなす仕様にしたいので、早速 `Post` のロジックを変えましょう：

```diff_swift:Post.swift
-struct Post: Hashable, Identifiable {
+struct Post {
    // ...
}
+
+extension Post: Identifiable {
+    var id: String { title }
+}
```

これでちゃんと想定通り、いいねボタンをタップしてもリスト画面に戻らない動きになります：

![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/fab3bbd3-c0c3-9d7f-f2bf-5f8eb8508b28.gif)

もちろん `id` 何を返すかは仕様と相談してください。今回は `title` にしていますが、例えば Qiita の記事でしたら、公開後にタイトルを変えることもあるので `title` を `id` として使えないと思います。その場合によくあるやり方としては最初に作るときに絶対に変わらない `let id = UUID()` を作ることです。他にも例えばチェーン店の店舗情報でしたら、管理情報として店舗コードもしくは管理番号などがあると思いますし、自動車の個体管理でしたら、必ず一意で変わらない車台番号[^car-number]がありますので、それらを `id` として使うのもアリです。大事なのは、**エンジニアとして責任を持って、これが同じなら同じオブジェクトと見做すものを定めること**です。

[^car-number]: 気をつけてほしいのは、車台番号は車のナンバープレートの、いわゆる登録番号ではありません。[車台番号](https://ja.wikipedia.org/wiki/車台番号)は車が製造される段階で打刻される一意の番号で、（オーナーが違法改造などしない限り）廃車されるまで変わることはありません；逆にナンバープレートの[登録番号](https://ja.wikipedia.org/wiki/日本のナンバープレート)は車が陸運局で正式登録されるまで交付されませんし、オーナーの引っ越しなどの都合で変わることもあります。また豆知識ですが、車台番号はあくまで日本国内での流通で使われるものであり、海外では [VIN コード](https://ja.wikipedia.org/wiki/車両識別番号)の使用が主流です。

今回の話についてもっと詳しく知りた方は、ぜひこちらの Apple 公式のセッション動画を見てみてください：
https://developer.apple.com/videos/play/wwdc2021/10022/