はじめに
HashableプロトコルとIdentifiableプロトコルって何のために使うの?違いは?と疑問に思ったので調べました。
もし間違いがありましたらご指摘いただけると幸いです。
Hashable
概要
その型の値を元にハッシュ値が計算可能であることを表します。
下記のHashableの定義から、HashableはEquatable
プロトコルを準拠しており、hashValue
プロパティとhash
メソッドを持っていることがわかります。
public protocol Hashable : Equatable {
/// The hash value.
///
/// Hash values are not guaranteed to be equal across different executions of
/// your program. Do not save hash values to use during a future execution.
///
/// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
/// conform to `Hashable`, implement the `hash(into:)` requirement instead.
var hashValue: Int { get }
/// Hashes the essential components of this value by feeding them into the
/// given hasher.
///
/// Implement this method to conform to the `Hashable` protocol. The
/// components used for hashing must be the same as the components compared
/// in your type's `==` operator implementation. Call `hasher.combine(_:)`
/// with each of these components.
///
/// - Important: Never call `finalize()` on `hasher`. Doing so may become a
/// compile-time error in the future.
///
/// - Parameter hasher: The hasher to use when combining the components
/// of this instance.
func hash(into hasher: inout Hasher)
}
Hashableへの準拠が必要なケース
ハッシュ値を用いて表現されるデータ構造において、Hashableに準拠された型の利用が必要になります。
代表的な一例ですが、Set、Dictionaryなどを利用する場合、Hashableに準拠した型が必須です。
Set
Setの定義を見ると、Element : Hashable
となっており、Hashableに準拠しなければいけないことがわかります。
@frozen public struct Set<Element> where Element : Hashable
そのため、Hashableに準拠していないインスタンスをSetに利用すると、エラーが発生します。
struct GridPoint {
var x: Int
var y: Int
}
var tappedPoints: Set = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y: 1)]
Generic struct 'Set' requires that 'GridPoint' conform to 'Hashable'
Hashableに準拠することでエラーが解消され、Setに利用できるようになります。
struct GridPoint: Hashable {
var x: Int
var y: Int
}
var tappedPoints: Set = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y: 1)]
Dictionaryのキー
Setと同様に、Dictionaryの定義から Key : Hashable
キーがHashableに準拠する必要があることがわかります。
@frozen public struct Dictionary<Key, Value> where Key : Hashable
そのため、Hashableに準拠していないインスタンスをDictionaryのキーに利用すると、エラーが発生します。
struct GridPoint {
var x: Int
var y: Int
}
let dictionary = [GridPoint(x: 2, y: 3): "point A", GridPoint(x: 4, y:1): "point B"]
Generic struct 'Dictionary' requires that 'GridPoint' conform to 'Hashable'
Hashableに準拠することでエラーが解消され、Setに利用できるようになります。
struct GridPoint: Hashable {
var x: Int
var y: Int
}
let dictionary = [GridPoint(x: 2, y: 3): "point A", GridPoint(x: 4, y:1): "point B"]
ForEach
SetやDictionaryと同様に、ForEachの定義から ID : Hashable
IDがHashableに準拠する必要があることがわかります。
public struct ForEach<Data, ID, Content> where Data : RandomAccessCollection, ID : Hashable
Set、Dictionaryと同じなので、Hashableに準拠しない(エラーになる)場合の記載は割愛します。
以下のようにForEachのidには、Hahsableへの準拠が必要になります。
struct GridPoint: Hashable {
var x: Int
var y: Int
}
struct ContentView: View {
let array = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y:1)]
var body: some View {
List {
ForEach(array, id: \.self, content: { elem in
Text("x:\(elem.x) y:\(elem.y)")
})
}
}
}
ForEachの場合、Identifiable
を準拠するケースもよくあるので、そちらについては次節で説明します。
Identifiable
概要
インスタンスが一意のIDを持つエンティティの値を保持するタイプのクラス。
下記のIdentifiableの定義を見ると、IDがHashable
を準拠していることがわかります。
public protocol Identifiable {
/// A type representing the stable identity of the entity associated with
/// an instance.
associatedtype ID : Hashable
/// The stable identity of the entity associated with this instance.
var id: Self.ID { get }
}
また、Identifiableを準拠すると、そのクラスや構造体はid
プロパティを持つ必要があります。
struct GridPoint: Identifiable {
let id = UUID()
var x: Int
var y: Int
}
Identifiableへの準拠が必要なケース
Hashableと似ていますが、一意に特定する必要がある要素において、Identifiableに準拠された型の利用が必要となるケースが多いです。
たくさんあるので、一例としてHashableでも出てきたForEach
について説明します。
ForEach
Hashableの例ではForEach内でidを引数指定していましたが、Identifiableを準拠したクラスや構造体の場合、そのクラスや構造体のプロパティにidがあるため、ForEach内では指定しません。
Hashableの場合
ForEach(array, id: \.self, content: { elem in
Identifiableの場合
ForEach(array, content: { elem in
ただ、今までと同様にIdentifiableを準拠していない場合、idが無いため、ForEachでエラーとなります。
struct GridPoint {
var x: Int
var y: Int
}
struct ContentView: View {
let array = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y:1)]
var body: some View {
List {
ForEach(array, content: { elem in
Text("x:\(elem.x) y:\(elem.y)")
})
}
}
}
なぜかこんなエラーが出ます。
Failed to produce diagnostic for expression; please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the project
Identifiableを準拠することでエラーが解消され、ForEachで利用できるようになります。
struct GridPoint: Identifiable {
let id = UUID()
var x: Int
var y: Int
}
struct ContentView: View {
let array = [GridPoint(x: 2, y: 3), GridPoint(x: 4, y:1)]
var body: some View {
List {
ForEach(array, content: { elem in
Text("x:\(elem.x) y:\(elem.y)")
})
}
}
}
Identifiable と Hashable の違い
では、IdentifiableとHashableの違いは何でしょうか?
こちらの記事によると、違いは以下のようです。
https://nshipster.com/identifiable/
- 識別子とは異なり、ハッシュ値は状態に依存し、オブジェクトが変更されると変化する。
- 識別子は起動間で安定しているが、ハッシュ値はランダムに生成されたハッシュシードによって計算されるため、起動間で不安定になる。
- 識別子は一意だが、ハッシュ値が衝突する可能性があるため、コレクションからフェッチするときに等価性チェックが必要になる。
- 識別子は意味のあるものになる可能性があるが、ハッシュ値は無秩序である。
つまりIdentifyとHashの言葉通り、識別子 ≠ ハッシュ値 となります。
最後に
普段何気なく利用していましたが、調べることで理解が深まり、意図を持って利用できるようになりました。
今後も疑問を持ったものは調べてまとめていければと思います。
閲覧いただきありがとうございました。