

自作したclassを集合を使った処理にかけたいです。  
そのために自作の型にHashableプロトコルに準拠させたいのですが、

swiftリファレンスの翻訳版より、

[code]

    struct GridPoint {
             var x: Int
             var y: Int
         }
    
    
    
    extension GridPoint: Hashable {
             var hashValue: Int {
                 return x.hashValue ^ y.hashValue
             }
    
             static func == (lhs: GridPoint, rhs: GridPoint) -> Bool {
                 return lhs.x == rhs.x && lhs.y == rhs.y
             }
         }
    
[/code]

この部分が理解することが難しいので質問させていただきました。

つまり何を定義しなくてはならないのでしょうか？


CC BY-SA 4.0
2019-06-21T06:35:33.700
55967
2019-06-22T10:09:35.597
29298
question
swift swift5
Swift,Hashableプロトコルの準拠に関して。


### 肝心の「自作したclass」に対する回答になっていなかったので、加筆修正しました。

(Thanks to nagonsoftware さん。)

* * *

`Hashable`のキモ(同様の機構を備える他の言語でも共通の考え方です)は、

  * `hashValue`を使って、等しくなる可能性があるかどうかチェックする

`hashValue`が等しくなければ、絶対に`==`にはならない

  * `hashValue`が等しい場合には、実際に等しいかどうか`==`を使って確認する

と言う使い方をするものだと言うことです。

* * *

現在のSwiftの場合`Hashable`は以下のように定義されています。

[code]

    public protocol Hashable : Equatable {
    
        var hashValue: Int { get }
    
        func hash(into hasher: inout Hasher)
    }
    
[/code]

また、`Equatable`の定義は以下の通りです。

[code]

    public protocol Equatable {
        static func == (lhs: Self, rhs: Self) -> Bool
    }
    
[/code]

(Swift言語自身のソースに入っているヘッダードキュメントにはすごく有用な内容が含まれているのですが、全部英語であることもあり、今回は割愛しました。)

要は、1つのメソッド、1つのプロパティと1つの演算子を定義すれば`Hashable`プロトコルには準拠することができます。

  * ハッシュ値を取得するプロパティ`hashValue`

  * ハッシュ値を逐次計算するメソッド`hash(into:)`

  * 等値性を判定する演算子`==`

現在のSwiftでは`hashValue`にはデフォルト実装が用意されているため、`hash(into:)`と`==`さえ準備すればあなたの定義した`struct`や`class`を`Hashable`に適合させることができます。

(どうしても自分でやりたいなら)こんな感じ:

[code]

    struct GridPoint {
        var x: Int
        var y: Int
    }
    
    extension GridPoint: Hashable {
        public func hash(into hasher: inout Hasher) {
            x.hash(into: &hasher)
            y.hash(into: &hasher)
        }
    
        static func == (lhs: GridPoint, rhs: GridPoint) -> Bool {
            return lhs.x == rhs.x && lhs.y == rhs.y
        }
    }
    
[/code]

(注:
`hash(into:)`ではなく、`hashValue`を直接実装すると言うのは、かなり古いやり方です。もう少し新しい情報源を探した方が良いでしょう。)

ここで1つ重要な点は、`hashValue`と`==`が整合性を保つためには、次の公理を満たしていなければいけない、と言うことです、。

### `a == b`ならば絶対に`a.hashValue == b.hashValue`が成立する

上に書いた定型的実装が、この公理を満たすことはすぐにはわかりにくいかもしれませんが、ちゃんと成立します。(長々と書いてもわかりやすく書ける自信がないので、ざっくりと端折らせていただきます。)

* * *

ただし、現在のSwift(4.2以降)では、`struct`と`enum`に対してはそのような定型的な処理を自分で書くことは滅多にありません。

`extension`とかを一切使わず、以下のような`struct`定義をどこかに書いて下さい。

[code]

    struct GridPoint: Hashable {
        var x: Int
        var y: Int
    }
    
[/code]

Swiftコンパイラは、何もエラーや警告を出さすに`GridPoint`を`Hashable`な型として受け入れるでしょう。もちろん`Set`や`Dictionary`の`Key`としても使えます。

提案[SE-0185 Synthesizing Equatable and Hashable
conformance](https://github.com/apple/swift-
evolution/blob/master/proposals/0185-synthesize-equatable-
hashable.md)(と、その後の修正である[SE-0206 Hashable
Enhancements](https://github.com/apple/swift-
evolution/blob/master/proposals/0206-hashable-
enhancements.md))が採用されて以降、Swiftコンパイラは(いくつかの制限がありますが)、`Hashable`と宣言されたデータ型のために自動的に`hashValue`,
`hash(into:)`および`==`の定義を作成してくれます。あまり悩まず、どんどん活用しましょう。

* * *

と言うわけで、「自作の型にHashableプロトコルに準拠させたい」と言うのが目的であるならば、

# 自作の型が`struct`や`enum`で、特に`==`に固有の修正がない場合

### 型宣言に`: Hashable`と言うのを付け加えなさい

と言うことになります。

もう少し深くその背景を知りたい、とか、自分の定義したデータ型ではうまくいかない、なんてことがあればコメント等でお知らせください。「集合を使った処理にかけたい」と言うのが目的なら、ちゃんと`Set`に使えるのもすぐに確認できるはずです。

# 自作の`class`の場合、あるいは`==`の動作を修正したい場合

## (ただし、`NSObject`のサブクラスは除く。)

上に書いたように`hash(into:)`メソッドと`==`演算子を整合性を持つように実装しなければいけません。ご自身の`class`がどのような定義になっているのかわからないので、一般的な場合で示さざるを得ませんが、例えば

### 1\. 2つのキーになるプロパティの値が等しければ`==`であると判定する場合

[code]

    class MyClass {
        var keyValue1: Int
        var keyValue2: String
        var otherValue1: Double = 0.0
        var otherValue2: [String] = []
    
        init(keyValue1: Int, keyValue2: String) {
            self.keyValue1 = keyValue1
            self.keyValue2 = keyValue2
        }
    }
    
    extension MyClass: Hashable {
        //`==`をあなたのクラスの表すものの意味に合わせて定義する
        static func == (lhs: MyClass, rhs: MyClass) -> Bool {
            //ここでは`keyValue1`と`keyValue2`が等しければ、2つの`MyClass`は`==`であるものとする
            return lhs.keyValue1 == rhs.keyValue1 && lhs.keyValue2 == rhs.keyValue2
        }
    
        public func hash(into hasher: inout Hasher) {
            //`==`の中で「等しいかどうか」の判定に使われているプロパティに対し`hash(into:)`を呼んでやる
            keyValue1.hash(into: &hasher)
            keyValue2.hash(into: &hasher)
        }
    }
    
[/code]

上に書いたようにまず自分が思う`==`を実装し、それに合わせて`hash(into:)`を実装します。

### 2\. 同一のインスタンスを参照していれば`==`であると判定する場合

[code]

    //MyClassの定義は上と同じとする
    
    extension MyClass: Hashable {
        static func == (lhs: MyClass, rhs: MyClass) -> Bool {
            //同じインスタンスを参照していれば、`==`であると定義する場合
            return lhs === rhs
        }
    
        public func hash(into hasher: inout Hasher) {
        //アドレスそのものが「等しいかどうか」の判定に使われているので、`ObjectIdentifier(self)`に対して`hash(into:)`を呼んでやる
            ObjectIdentifier(self).hash(into: &hasher)
        }
    }
    
[/code]

## `NSObject`のサブクラスの場合

`NSObject`はデフォルトで`Hashable`となっていますが、`hashValue`と`==`のデフォルト実装が以下のように純Swiftクラスとは異なっています。

  * `hashValue`のデフォルト実装は、[`NSObjectProtocol.hash`](https://developer.apple.com/documentation/objectivec/nsobjectprotocol/1418859-hash)を呼び出す
  * `==`のデフォルト実装は[`NSObjectProtocol.isEqual(_:)`](https://developer.apple.com/documentation/objectivec/nsobjectprotocol/1418795-isequal)を呼び出す

従って、動作をカスタマイズする場合、`hash(into:)`ではなく`hash`プロパティを、`==`演算子ではなく、`isEqual(_:)`メソッドをオーバライドする必要があります。

[code]

    class MyNSClass: NSObject { //<- `NSObject`の子孫クラスは常に`Hashable`と扱われる
        var keyValue1: Int
        var keyValue2: String
        var otherValue1: Double = 0.0
        var otherValue2: [String] = []
    
        init(keyValue1: Int, keyValue2: String) {
            self.keyValue1 = keyValue1
            self.keyValue2 = keyValue2
        }
    
        //`==`の動作を変えたい場合、`isEqual(_:)`をオーバライドする
        override func isEqual(_ object: Any?) -> Bool {
            if let rhs = object as? MyNSClass {
                return self.keyValue1 == rhs.keyValue1 && self.keyValue2 == rhs.keyValue2
            } else {
                return false
            }
        }
    
        //`hash`プロパティは`isEqual(_:)`と整合性のある実装にする
        override var hash: Int {
            var hasher = Hasher()
            keyValue1.hash(into: &hasher)
            keyValue2.hash(into: &hasher)
            return hasher.finalize()
        }
    }
    
[/code]

* * *

あなたの「自作したclass」の中身がわからないため、一般的なことしか書けなかったので少し見にくいかもしれません。「自作したclass」についてのご質問であればそのclassの定義を示していただいた方が、より適切な回答をより早く得られることにつながります。


CC BY-SA 4.0
2019-06-21T12:10:28.593
55982
2019-06-22T10:09:35.597
2019-06-22T10:09:35.597
13972
13972
55967
answer