この記事は何?
SwiftプログラミングにおけるHashable
プロトコルについて、公式のヘルプドキュメントを翻訳および解説します。
参考: Hashable | Apple Developer Documantation
宣言
protocol Hashable : Equatable
概要
Hashable
プロトコルに適合した型は、セットや辞書のキーとして使用できます。
標準ライブラリの型は、その多くがHashable
プロトコルに適合しています。
文字列、整数、浮動小数点数、ブール値、そしてセットもデフォルトでハッシュ化可能です。
オプショナル、配列、範囲などの他のいくつかの型は、実装する型パラメータが適合すると、自動的にハッシュ可能になります。
独自に定義した型もハッシュ化可能です。
例えば、関連値のない列挙型は、定義するだけで自動的にHashable
プロトコルに適合します。
また、格納プロパティがすべてHashable
な構造体や、関連値がすべてHashable
な列挙型については、コンパイラが自動的にhash(into:)
の実装を提供します。
「値をハッシュする」ことは、その値の本質的な構成要素をHasher
型で表されるハッシュ関数に引き渡すことを意味します。
本質的な構成要素とは、その型が「どのようにEquatable
を実装したか」に依存します。
互いに等しいインスタンス同士は同じ順序で、同じ値をhash(into:)
メソッドのHasher
に与える必要があります。
Hashableプロトコルへの適合
独自に定義した型をセットまたは辞書のキーとして使用するには、その型をHashable
プロトコルに適合させます。
Hashable
プロトコルはEquatable
を継承しているため、そのプロトコルの要件も満たす必要があります。
元となる型がHashable
を採用した上で、以下に挙げる条件を満たしている場合、コンパイラはHashable
への適合に必要な実装を自動的に生成します。
- 構造体の場合、すべての格納プロパティが
Hashable
に適合している。 - 列挙型の場合、すべての関連値が
Hashable
に適合している(関連値のない列挙型は宣言なしでもHashable
に適合します)。
型のHashable
適合性をカスタマイズしたり、上記の基準を満たさない型にHashable
を採用したり、既存の型を拡張してHashable
に適合させるには、独自にhash(into:)
メソッドを実装してください。
hash(into:)
メソッドの実装では、提供されたHasher型インスタンスに対して、カスタム型の重要な構成要素とともにcombine(_:)
メソッドを呼び出します。
Hashable
およびEquatable
プロトコルのセマンティック要件を確実に満たすために、型のEquatable
適合性もカスタマイズすることをお勧めします。
例として、ボタンのグリッド位置を記述するGridPoint
型を考えます。
以下では、最初にGridPoint
型を宣言します。
/// x軸とy軸からなる平面座標における点のモデル
struct GridPoint {
var x: Int
var y: Int
}
次に、ユーザーがタップしたことのある位置を示す「グリッドポイントのセット」を作成します。
GridPoint
型はまだHashable
プロトコルに適合していないので、セットで使用することはできません。
Hashable
に適合させるには等価演算子関数==
を実装してから、hash(into:)
メソッドも実装してください。
extension GridPoint: Hashable {
static func == (lhs: GridPoint, rhs: GridPoint) -> Bool {
return lhs.x == rhs.x && lhs.y == rhs.y
}
func hash(into hasher: inout Hasher) {
hasher.combine(x)
hasher.combine(y)
}
}
この例のhash(into:)
メソッドは、提供されたハッシャーに「x
とy
のプロパティ」をフィードします。
これらのプロパティは、等価演算子関数==
で「互いに等しいかどうか」を比較するために使用されるものと同じです。
GridPoint
がHashable
プロトコルに適合できたので、タップされたグリッドポイントのセットを作成できるようになりました。
var tappedPoints: Set = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y: 1)]
let nextTap = GridPoint(x: 0, y: 1)
if tappedPoints.contains(nextTap) {
print("Already tapped at (\(nextTap.x), \(nextTap.y)).")
} else {
tappedPoints.insert(nextTap)
print("New tap detected at (\(nextTap.x), \(nextTap.y)).")
}
// Prints "New tap detected at (0, 1).")