LoginSignup
13
9

More than 1 year has passed since last update.

【Swift】Hashable, Identifiableについて

Last updated at Posted at 2021-05-14

はじめに

HashableプロトコルとIdentifiableプロトコルって何のために使うの?違いは?と疑問に思ったので調べました。
もし間違いがありましたらご指摘いただけると幸いです。

Hashable

概要

その型の値を元にハッシュ値が計算可能であることを表します。
下記のHashableの定義から、HashableはEquatableプロトコルを準拠しており、hashValueプロパティとhashメソッドを持っていることがわかります。

Hashable
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に準拠しなければいけないことがわかります。

Setの定義
@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に準拠する必要があることがわかります。

Dictionaryの定義
@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に準拠する必要があることがわかります。

ForEachの定義
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を準拠していることがわかります。

Identifiable
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の言葉通り、識別子 ≠ ハッシュ値 となります。

最後に

普段何気なく利用していましたが、調べることで理解が深まり、意図を持って利用できるようになりました。
今後も疑問を持ったものは調べてまとめていければと思います。
閲覧いただきありがとうございました。

13
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
9