Swift でアプリを作るとき、よく登場するのが Hashable。
この記事では、Hashable が果たす役割、Equatable との関係を具体例を交えて丁寧に解説します。
Hashableとは?
Hashable は、値から「ハッシュ値」と呼ばれる数値を計算できることを表すプロトコルです。ハッシュ値は、値を一意に区別するための「指紋」のようなものだとイメージするとわかりやすいです。
Swift の定義としては、Hashable は Equatable を継承しており、次のようなイメージです:
protocol Hashable : Equatable {
func hash(into hasher: inout Hasher)
}
つまり、Hashable に準拠する型は:
- 等価比較(==)ができる(Equatable)
- ハッシュ値を計算できる(Hashable)
という二つの性質を持つことになります。
なぜHashableが必要なのか?
Hashable が本領を発揮するのは、次のような場面です。
- Set<T> の要素として使うとき
- Dictionary<Key, Value> の Key として使うとき
- 高速に「同じ値かどうか」比較したいとき
- SwiftUI が内部で差分計算(Diffing)を行うとき
例えば、ユーザーを一意に管理したい場合、Set を使うと便利です:
struct User: Hashable {
let id: String
let name: String
}
var users: Set<User> = []
users.insert(User(id: "1", name: "Taro"))
users.insert(User(id: "1", name: "Taro")) // 同じUserなので重複しない
このとき、Set は内部で:
- ユーザーのハッシュ値を計算する
- 既に同じハッシュ値+同じ内容の要素があるかチェックする
という処理を行っています。これが、Hashable が「高速比較」に向いていると言われる理由です。
Hashable と Equatable の違い
よく混同されやすいのが Equatable と Hashable です。
- Equatable:2つの値が「等しいかどうか」を判定できる(== が使える)
- Hashable:Equatable の上に、「ハッシュ値を計算できる」性質が追加されたもの
つまり、Hashable に準拠している型は、Equatable としても扱えます。
let a = User(id: "1", name: "Taro")
let b = User(id: "1", name: "Taro")
if a == b {
print("同じユーザー")
}
この == による比較が成り立つことが、Hashable を正しく機能させる前提条件になっています。
Hashable で本来必要な記述
struct を Hashable に準拠させる場合、本来は次のようなコードを書く必要があります:
struct Post: Hashable {
let id: String
let authorId: String
let text: String
let createdAt: Date
// 本来は hash(into:) を実装する必要がある
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(authorId)
hasher.combine(text)
hasher.combine(createdAt)
}
// Equatable も同時に実装する必要がある
static func == (lhs: Post, rhs: Post) -> Bool {
lhs.id == rhs.id &&
lhs.authorId == rhs.authorId &&
lhs.text == rhs.text &&
lhs.createdAt == rhs.createdAt
}
}しかし Swift の struct は、次の2つの条件を満たしていれば、これらの実装を 自動で生成(自動合成)してくれます:
- すべての stored property が
Hashableに準拠している hash(into:)や==を自分で実装していない
そのため実際には、次のように書くだけで Hashable と Equatable が自動で付いてきます:
struct Post: Hashable {
let id: String
let authorId: String
let text: String
let createdAt: Date
}SwiftUI と Hashable の関係
SwiftUI の文脈では、Hashable は主に「差分計算」と「コレクション操作」のために使われます。
- List や ForEach が内部でモデルを比較する
- 状態の変化に応じて UI を最小限だけ更新する
- アニメーションやトランジションの対象を特定する
実際のコードとしては、Identifiable と組み合わせて次のような定義になることが多いです:
struct Post: Identifiable, Hashable {
let id: String
let authorId: String
let text: String
let createdAt: Date
}
struct TimelineView: View {
let posts: [Post]
var body: some View {
List(posts) { post in
Text(post.text)
}
}
}
ここで Hashable に準拠していることで、SwiftUI が内部で差分を効率的に計算できるようになり、タイムラインの更新がスムーズかつ高速になります。
Hashableを自前で実装するケース
ほとんどのケースでは自動合成で十分ですが、次のようなケースでは自前で hash(into:) を実装することもあります。
- 特定のプロパティだけを比較・ハッシュ対象にしたい
- 巨大な構造体でハッシュ計算コストを抑えたい
struct User: Hashable {
let id: String
let name: String
let profileText: String
func hash(into hasher: inout Hasher) {
// id だけをハッシュの対象にする
hasher.combine(id)
}
static func == (lhs: User, rhs: User) -> Bool {
lhs.id == rhs.id
}
}
このように、「何をもって同一とみなすか」を制御したい場合には、Hashable と Equatable を手動実装することがあります。ただし、基本的には自動合成で問題ないケースが多いため、必要性が出てきたときにだけ検討するくらいで十分です。
まとめ
- Hashable は「ハッシュ値を計算できる」という性質を表すプロトコル
- Set の要素、Dictionary のキーとして使うために必要
- 内部的には Equatable(==で比較可能)でもある
- 多くの struct は自動合成で Hashable を満たせる
- SwiftUI の差分更新やモデル比較にも活用される
- Identifiable(id による識別)と組み合わせると、リストUIとの相性が非常に良い
「とりあえず Hashable を付ける」から一歩進んで、
「なぜ Hashable が必要なのか」「どんな場面で効いているのか」を理解しておくと、モデル設計や SwiftUI の挙動がぐっとクリアに見えてきます。
