isEqual:およびハッシュをオーバーライドするためのベストプラクティス


267

isEqual:Objective-Cではどのように適切にオーバーライドしますか?「キャッチ」は、2つのオブジェクトが等しい場合(isEqual:メソッドによって決定される)、それらは同じハッシュ値を持つ必要があるようです。

Cocoa Fundamentals GuideIntrospectionセクションには、オーバーライドする方法の例があり、次のようにコピーされます。isEqual:MyWidget

- (BOOL)isEqual:(id)other {
    if (other == self)
        return YES;
    if (!other || ![other isKindOfClass:[self class]])
        return NO;
    return [self isEqualToWidget:other];
}

- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
    if (self == aWidget)
        return YES;
    if (![(id)[self name] isEqual:[aWidget name]])
        return NO;
    if (![[self data] isEqualToData:[aWidget data]])
        return NO;
    return YES;
}

ポインタの等価性をチェックし、次にクラスの等価性をチェックし、最後isEqualToWidget:namedataプロパティをチェックするだけを使用してオブジェクトを比較します。例に示されていないのは、オーバーライドする方法hashです。

たとえば、平等に影響を与えないプロパティが他にもあるとしましょうage。すべきではないhash方法が唯一のよう上書きすることnamedataハッシュに影響を与えますか?もしそうなら、あなたはそれをどのように行いますか?ただのハッシュを追加するnamedata?例えば:

- (NSUInteger)hash {
    NSUInteger hash = 0;
    hash += [[self name] hash];
    hash += [[self data] hash];
    return hash;
}

それで十分ですか?より良いテクニックはありますか?あなたがのようなプリミティブを持っている場合はどうなりますintか?それらを変換しNSNumberてハッシュを取得しますか?または構造体NSRect

Brain fart:もともとは「ビット単位OR」と一緒に|=。Meant add と一緒に書いたものです。)


2
if (![other isKindOfClass:[self class]])-これは技術的には、平等は可換ではないことを意味します。つまり、A = BはB = Aを意味しません(たとえば、一方が他方のサブクラスである場合)
Robert

ドキュメントのリンクが機能しなくなり、イントロスペクションに
jedwidz

回答:


111

皮切りに

 NSUInteger prime = 31;
 NSUInteger result = 1;

次に、プリミティブごとに

 result = prime * result + var

オブジェクトの場合、nilには0を使用し、それ以外の場合はハッシュコードを使用します。

 result = prime * result + [var hash];

ブール値の場合、2つの異なる値を使用します

 result = prime * result + ((var)?1231:1237);

説明と帰属

これはtcurdtの仕事ではなく、コメントはより多くの説明を求めていたので、帰属の編集は公平だと思います。

このアルゴリズムは「Effective Java」という本で普及し、現在、関連する章がオンラインでここにあります。その本はアルゴリズムを普及させ、現在では多くのJavaアプリケーション(Eclipseを含む)のデフォルトとなっています。ただし、Dan BernsteinまたはChris Torekにさまざまな原因があると考えられるさらに古い実装から派生したものです。その古いアルゴリズムは元々Usenetにあり、特定の帰属は困難です。たとえば、このApacheコードには、元のソースを参照する興味深い注釈(名前を検索)があります。

要するに、これは非常に古く、単純なハッシュアルゴリズムです。これは最もパフォーマンスが高くはなく、数学的には「良い」アルゴリズムであることが証明されていません。しかし、それは単純であり、多くの人々が長い間それを使用して良好な結果を得ているので、多くの歴史的なサポートがあります。


9
1231:1237はどこから来たのですか?JavaのBoolean.hashCode()でも見られます。それは魔法ですか?
David Leonard

17
衝突が発生するのは、ハッシュアルゴリズムの性質です。だから、私はあなたの主張を理解していません、ポール。
tcurdt

85
私の意見では、この回答は実際の質問(NSObjectのハッシュをオーバーライドするためのベストプラクティス)には応答しません。特定のハッシュアルゴリズムを提供するだけです。その上、説明が少ないため、深い知識がなければ理解するのが難しく、何をしているのかわからずに利用してしまう可能性があります。この質問に多くの賛成票がある理由がわかりません。
Ricardo Sanchez-Saez、2011年

6
1番目の問題-(int)は小さく、オーバーフローしやすいため、NSUIntegerを使用します。2番目の問題-結果に各変数ハッシュを掛け続けると、結果がオーバーフローします。例えば。[NSString hash]は大きな値を作成します。5つ以上の変数がある場合、このアルゴリズムでオーバーフローするのは簡単です。結果として、すべてが同じハッシュにマッピングされますが、これは悪いことです。私の応答をご覧ください:stackoverflow.com/a/4393493/276626
Paul Solt

10
@PaulSolt-ハッシュの生成ではオーバーフローは問題ではなく、衝突です。ただし、オーバーフローによって衝突が発生する可能性が高くなるとは限りません。オーバーフローが原因ですべてが同じハッシュにマッピングされるというステートメントは、単に正しくありません。
DougW

81

私はObjective-Cを自分で手に取っているだけなので、その言語について具体的に話すことはできませんが、他の言語では、2つのインスタンスが「等しい」場合、同じハッシュを返す必要があります。それらをハッシュテーブル(または任意の辞書タイプのコレクション)のキーとして使用しようとすると、ある種の問題が発生します。

一方、2つのインスタンスが等しくない場合、それらは同じハッシュを持つ場合と持たない場合があります。そうでない場合が最適です。これがハッシュテーブルのO(1)検索とO(N)検索の違いです。すべてのハッシュが衝突した場合、テーブルを検索することはリストを検索することよりも優れていることに気付くかもしれません。

ベストプラクティスの観点から、ハッシュは入力に対して値のランダムな分布を返す必要があります。これは、たとえば、doubleがあり、値の大部分が0から100の間でクラスター化する傾向がある場合、それらの値によって返されるハッシュが、可能なハッシュ値の範囲全体に均等に分散されていることを確認する必要があることを意味します。これにより、パフォーマンスが大幅に向上します。

いくつかのハッシュアルゴリズムがあり、そのうちいくつかはここにリストされています。パフォーマンスに大きな影響を与える可能性があるため、新しいハッシュアルゴリズムを作成しないようにしています。したがって、既存のハッシュメソッドを使用し、例で行うように、ある種のビットごとの組み合わせを行うことは、それを回避する良い方法です。


4
+1すばらしい答えです。特に「ベストプラクティス」と、優れた(一意の)ハッシュが重要である理由の背後にある理論について実際に語っているので、より多くの投票に値します。
クインテイラー、

30

重要なプロパティのハッシュ値に対する単純なXORで、99%の時間で十分です。

例えば:

- (NSUInteger)hash
{
    return [self.name hash] ^ [self.data hash];
}

Mattt Thompsonがhttp://nshipster.com/equality/で見つけた解決策(この質問にも彼の投稿で言及されています!)


1
この回答の問題は、プリミティブ値をまったく考慮しないことです。また、プリミティブ値はハッシュにも重要です。
Vive

@Viveこれらの問題のほとんどはSwiftで解決されますが、これらのタイプはプリミティブであるため、通常は独自のハッシュを表します。
Yariv Nissim 2017年

1
あなたはSwiftに適していますが、それでもobjcで書かれたプロジェクトはたくさんあります。あなたの答えはobjcに捧げられているので、少なくとも言及する価値があります。
2017年

ハッシュ値をXORすることは悪いアドバイスです、それは多くのハッシュ衝突につながります。代わりに、他の答えが示すように、素数で乗算してから加算します。
fishinear

27

このスレッドは、自分isEqual:hashメソッドを1つのキャッチで実装するために必要なすべてを提供するのに非常に役立つことがわかりました。isEqual:サンプルコードでオブジェクトインスタンス変数をテストする場合、以下を使用します。

if (![(id)[self name] isEqual:[aWidget name]])
    return NO;

ユニットテストでオブジェクトが同一であること知ったときに、エラーなしで繰り返し失敗しました(つまりNOが返されました)。その理由は、インスタンス変数の1つがnilだったため、上記のステートメントは次のとおりでした。NSString

if (![nil isEqual: nil])
    return NO;

以来とnilのいずれかの方法に応答します、これは完全に合法であるが、

[nil isEqual: nil]

NOであるnilを返すため、オブジェクトとテスト対象のオブジェクトの両方にnilオブジェクトがある場合、それらは等しくないと見なされます(つまりNOを返します)。isEqual:

この簡単な修正は、ifステートメントを次のように変更することでした。

if ([self name] != [aWidget name] && ![(id)[self name] isEqual:[aWidget name]])
    return NO;

彼らは両方のであれば、この方法では、それらのアドレスが同じであれば、それは関係なく、メソッドの呼び出しをスキップしないゼロまたは同じオブジェクトへの両方のポインティングが、どちらかではない場合はnilか、彼らはその後、コンパレータが適切と呼ばれる別のオブジェクトを指します。

これにより、誰かが頭を掻く数分を節約できることを願っています。


20

ハッシュ関数は、別のオブジェクトのハッシュ値と衝突したり一致したりする可能性が低い半一意の値を作成する必要があります。

これが完全なハッシュ関数で、クラスのインスタンス変数に適合させることができます。64/32ビットアプリケーションとの互換性のために、intではなくNSUIntegerを使用しています。

異なるオブジェクトで結果が0になると、ハッシュが衝突するリスクがあります。ハッシュ関数に依存する一部のコレクションクラスを操作する場合、ハッシュが衝突するとプログラムが予期しない動作をする可能性があります。ハッシュ関数を使用する前に必ずテストしてください。

-(NSUInteger)hash {
    NSUInteger result = 1;
    NSUInteger prime = 31;
    NSUInteger yesPrime = 1231;
    NSUInteger noPrime = 1237;

    // Add any object that already has a hash function (NSString)
    result = prime * result + [self.myObject hash];

    // Add primitive variables (int)
    result = prime * result + self.primitiveVariable; 

    // Boolean values (BOOL)
    result = prime * result + (self.isSelected?yesPrime:noPrime);

    return result;
}

3
ここで1つ問題があります。ドット構文を避けたいので、BOOLステートメントを(eg)に変換しましたresult = prime * result + [self isSelected] ? yesPrime : noPrime;。次に、これがresult(eg)に設定されていることに気づきました。これは1231?オペレーターが優先するためと思います。私は、ブラケットを追加することによって、問題を修正:result = prime * result + ([self isSelected] ? yesPrime : noPrime);
アシュリー

12

簡単だが非効率的な方法は-hash、すべてのインスタンスに対して同じ値を返すことです。それ以外の場合は、はい、同等性に影響するオブジェクトのみに基づいてハッシュを実装する必要があります。で緩やかな比較を使用する場合、これはトリッキーです-isEqual:(たとえば、大文字と小文字を区別しない文字列比較)。intの場合、NSNumbersと比較しない限り、通常はint自体を使用できます。

| =は使用しないでください。飽和します。代わりに^ =を使用してください。

ランダムな楽しい事実:[[NSNumber numberWithInt:0] isEqual:[NSNumber numberWithBool:NO]]、しかし[[NSNumber numberWithInt:0] hash] != [[NSNumber numberWithBool:NO] hash]。(rdar:// 4538282、2006年5月5日からオープン)


1
| =はまさにその通りです。本当にそういう意味ではありませんでした。:) + =と^ =はかなり同等です。doubleやfloatなどの整数以外のプリミティブをどのように処理しますか?
Dave Dribin、2008年

ランダムな面白い事実:Snow Leopardでテストしてみてください;-)
Quinn Taylor

フィールドをハッシュに結合するためにORではなくXORを使用することについて彼は正しいです。ただし、すべてのオブジェクトに同じ-hash値を返すのアドバイスを使用していない-それは簡単、それは深刻な性能低下する可能性がありますが、何もオブジェクトのハッシュを使用しています。ハッシュはありません持って等しくないオブジェクトについては異なることがありますが、それを達成することができた場合、そのようなものは何もありません。
クインテイラー、

未解決のレーダーバグレポートはクローズされています。openradar.me/4538282どういう意味ですか?
JJD 2010年

JJD、クインが示唆したように、バグはMac OS X 10.6で修正されました。(コメントは2年前のものであることに注意してください。)
Jens Ayton 2010年

9

isEqualtrue の場合に等しいハッシュのみを提供する必要があることに注意してください。場合はisEqualfalseで、ハッシュは、おそらくそれはですが、不平等である必要はありません。したがって:

ハッシュは単純にしてください。最も特徴的なメンバー(またはいくつかのメンバー)変数を選択します。

たとえば、CLPlacemarkの場合、名前だけで十分です。はい、まったく同じ名前の2つまたは3つの異なるCLPlacemarkがありますが、それらはまれです。そのハッシュを使用します。

@interface CLPlacemark (equal)
- (BOOL)isEqual:(CLPlacemark*)other;
@end

@implementation CLPlacemark (equal)

...

-(NSUInteger) hash
{
    return self.name.hash;
}


@end

市や国などを指定する必要はありません。名前で十分です。おそらく名前とCLLocation。

ハッシュは均等に分散する必要があります。したがって、キャレット^(xor記号)を使用して複数のメンバー変数を組み合わせることができます

だからそれはのようなものです

hash = self.member1.hash ^ self.member2.hash ^ self.member3.hash

これにより、ハッシュが均等に分散されます。

Hash must be O(1), and not O(n)

では、配列で何をすべきか?

繰り返しますが、簡単です。配列のすべてのメンバーをハッシュする必要はありません。最初の要素、最後の要素、カウント、おそらくいくつかの中間要素をハッシュするのに十分であり、それだけです。


ハッシュ値のXORを実行しても、均等に分散されません。
15:09

7

ちょっと待ってください。これを行うにははるかに簡単な方法ですが、最初にオーバーライド- (NSString )descriptionして、オブジェクトの状態の文字列表現を提供することです(オブジェクトの状態全体をこの文字列で表す必要があります)。

次に、次の実装を提供しますhash

- (NSUInteger)hash {
    return [[self description] hash];
}

これは、「2つの文字列オブジェクトが等しい場合(isEqualToString:メソッドで決定)、それらは同じハッシュ値でなければならない」という原則に基づいています。

ソース:NSStringクラスリファレンス


1
これは、記述方法が一意であることを前提としています。説明のハッシュを使用すると、明白ではない可能性がある依存関係が作成され、衝突のリスクが高くなります。
Paul Solt

1
+1賛成。これは素晴らしいアイデアです。説明が衝突を引き起こすことを恐れている場合は、それを上書きできます。
user4951 2012

ジムに感謝します。これがちょっとしたハックであることは否定しませんが、とにかくそれはうまくいくと思いますdescription。上位投票ソリューションのいずれか。数学的に最もエレガントなソリューションではないかもしれませんが、トリックを実行する必要があります。Brian B.が述べているように(現時点で最も賛成されている回答)、「私は新しいハッシュアルゴリズムを作成しないようにしています」-同意しました!-私hashNSString
ジョナサンエリス

それはきちんとした考えなので賛成です。追加のNSString割り当てを恐れているので、私はそれを使用しません。
Karwag 2013年

1
ほとんどのクラスdescriptionにはポインタアドレスが含まれているため、これは一般的なソリューションではありません。したがって、これにより、同じクラスの2つの異なるインスタンスが異なるハッシュで等しくなります。これは、2つの等しいオブジェクトが同じハッシュを持つという基本的な仮定に違反します。
ディオゴT

5

等号とハッシュのコントラクトは、Javaの世界では十分に指定され、徹底的に調査されていますが(@mipardiの回答を参照)、Objective-Cにも同じ考慮事項が当てはまるはずです。

EclipseはこれらのメソッドをJavaで生成するという信頼できる仕事をしているので、Objective-Cに手作業で移植したEclipseの例を次に示します。

- (BOOL)isEqual:(id)object {
    if (self == object)
        return true;
    if ([self class] != [object class])
        return false;
    MyWidget *other = (MyWidget *)object;
    if (_name == nil) {
        if (other->_name != nil)
            return false;
    }
    else if (![_name isEqual:other->_name])
        return false;
    if (_data == nil) {
        if (other->_data != nil)
            return false;
    }
    else if (![_data isEqual:other->_data])
        return false;
    return true;
}

- (NSUInteger)hash {
    const NSUInteger prime = 31;
    NSUInteger result = 1;
    result = prime * result + [_name hash];
    result = prime * result + [_data hash];
    return result;
}

そしてYourWidget、プロパティを追加するサブクラスの場合serialNo

- (BOOL)isEqual:(id)object {
    if (self == object)
        return true;
    if (![super isEqual:object])
        return false;
    if ([self class] != [object class])
        return false;
    YourWidget *other = (YourWidget *)object;
    if (_serialNo == nil) {
        if (other->_serialNo != nil)
            return false;
    }
    else if (![_serialNo isEqual:other->_serialNo])
        return false;
    return true;
}

- (NSUInteger)hash {
    const NSUInteger prime = 31;
    NSUInteger result = [super hash];
    result = prime * result + [_serialNo hash];
    return result;
}

この実装により、isEqual:Apple のサンプルにあるサブクラス化の落とし穴がいくつか回避されます。

  • Appleのクラステストother isKindOfClass:[self class]は、の2つの異なるサブクラスに対して非対称ですMyWidget。等式は対称である必要があります:b = aの場合のみ、a = bです。これは、テストをother isKindOfClass:[MyWidget class]に変更することで簡単に修正でき、すべてのMyWidgetサブクラスは相互に比較できます。
  • isKindOfClass:サブクラステストを使用すると、サブクラスisEqual:が洗練された等価テストで上書きされるのを防ぐことができます。これは、等式が推移的である必要があるためです。a= bおよびa = cの場合、b = cです。場合MyWidgetインスタンスが2つのに等しい比較YourWidget例では、それらのYourWidgetインスタンスがあっても、その場合、互いに等しい必要がありますserialNo異なります。

2番目の問題は、オブジェクトがまったく同じクラスに属している場合にオブジェクトが等しいと見なすことで修正できるため、[self class] != [object class]ここでテストします。一般的なアプリケーションクラスの場合、これが最善のアプローチのようです。

ただし、isKindOfClass:テストが望ましい場合もあります。これは、アプリケーションクラスよりもフレームワーククラスの方が一般的です。たとえば、/の区別に関係なく、またクラスクラスター内のどのプライベートクラスが関係しているかに関係なく、any NSStringNSString同じ基本文字シーケンスを持つ他のものと同等に比較する必要があります。NSStringNSMutableStringNSString

そのような場合、isEqual:は明確に定義され、十分に文書化された動作をする必要があり、サブクラスがこれをオーバーライドできないことを明確にする必要があります。Javaでは、「オーバーライドなし」の制限は、equalsメソッドとhashcodeメソッドにとしてフラグを立てることによって適用できますfinalが、Objective-Cには同等のものはありません。


@adubrそれは私の最後の2つの段落でカバーされています。MyWidgetクラスクラスターではないと理解されているため、これは焦点ではありません。
jedwidz 14

5

これはあなたの質問に直接答えることはありません(まったく)が、私は以前にMurmurHashを使用してハッシュを生成しました:murmurhash

理由を説明する必要があると思います:murmurhashは非常に高速です...


2
乱数を使用したvoid *キーの一意のハッシュに焦点を当てた(そしてObjective-Cオブジェクトにも関連しない)C ++ライブラリは、ここではあまり役に立ちません。-hashメソッドは毎回一貫した値を返す必要があります。そうしないと、まったく役に立たなくなります。オブジェクトが-hashを呼び出すコレクションに追加され、毎回新しい値を返す場合、重複は検出されず、コレクションからオブジェクトを取得することもできません。この場合、「ハッシュ」という用語は、セキュリティ/暗号化における意味とは異なります。
クインテイラー

3
murmurhashは暗号化ハッシュ関数ではありません。間違った情報を投稿する前に、事実を確認してください。Murmurhash は非常に高速であるため、カスタムObjective-Cクラスのハッシュに使用できます(特に、NSDataが多数含まれている場合)。ただし、「objective-cを取得しているだけ」の誰かに与えるのが最善のアドバイスではないことを示唆している可能性もありますが、質問に対する元の返信の接頭辞に注意してください。
シュワ

5

このページは、equals-およびhash-typeオーバーライドメソッドのガイドとして役立つことがわかりました。ハッシュコードを計算するためのまともなアルゴリズムが含まれています。このページはJavaを対象としていますが、Objective-C / Cocoaに簡単に適合させることができます。


1
archive.orgを経由してキャッシュされたリンク:web.archive.org/web/20071013053633/http://www.geocities.com/...
cobbal

4

私もObjective Cの初心者だけど、私はObjective Cの中のアイデンティティ対平等に関する優れた記事見つけ、ここを。私が読んだところ、デフォルトのハッシュ関数(一意のIDを提供する必要があります)を保持し、isEqualメソッドを実装してデータ値を比較できるようになっているようです。


私はCocoa / Objective Cの初心者です。この回答とリンクは、上記のすべてのより高度なものを収益に結び付けるのに本当に役立ちました-ハッシュについて心配する必要はありません-isEqual:メソッドを実装するだけです。ありがとう!
ジョンギャラガー、

@ceperryのリンクをお見逃しなく。Equality vs Identityカールクラフトの記事は本当に良いです。
JJD 2010年

6
@ジョン:私はあなたが記事をもう一度読むべきだと思います。「等しいインスタンスは同じハッシュ値を持つ必要がある」と非常に明確に述べています。オーバーライドする場合は、オーバーライドisEqual:する必要もありますhash
Steve Madsen、2011年

3

クインはつぶやきハッシュへの参照がここでは役に立たないのは間違いです。クインは、ハッシュの背後にある理論を理解したいのは正しいです。つぶやきは、その理論の多くを実装に蒸留します。この特定のアプリケーションにその実装を適用する方法を理解することは、探索する価値があります。

ここで重要なポイントのいくつか:

tcurdtの関数例は、'31 'が素数であるため、適切な乗数であることを示唆しています。プライムであることは必要かつ十分な条件であることを示す必要があります。実際、31 == -1%32であるため、31(および7)はおそらく特に優れた素数ではありません。約半分のビットが設定され、半分のビットがクリアされている奇数乗数がより優れている可能性があります。(つぶやきハッシュ乗算定数にはその性質があります。)

このタイプのハッシュ関数は、乗算した後、シフトとxorを使用して結果値を調整した場合、より強力になる可能性があります。乗算は、レジスタの上限で多くのビット相互作用の結果を生成し、レジスタの下端で相互作用の結果を低くする傾向があります。シフトとxorは、レジスタの下端での相互作用を増やします。

約半分のビットがゼロであり、約半分のビットが1である値に初期結果を設定することも役立つ傾向があります。

要素が結合される順序に注意することは有用かもしれません。おそらく最初に、値が強く分散されていないブール値やその他の要素を処理する必要があります。

計算の最後に、ビットスクランブルステージをいくつか追加すると便利な場合があります。

このアプリケーションで雑音雑音が実際に高速であるかどうかは、未解決の問題です。つぶやきハッシュは、各入力ワードのビットを事前混合します。複数の入力ワードを並行して処理できるため、複数の問題をパイプライン化したCPUを使用できます。


3

プロパティ名取得するための@tcurdtの回答と@ oscar-gomezの回答を組み合わせると、isEqualとハッシュの両方の簡単なドロップインソリューションを作成できます。

NSArray *PropertyNamesFromObject(id object)
{
    unsigned int propertyCount = 0;
    objc_property_t * properties = class_copyPropertyList([object class], &propertyCount);
    NSMutableArray *propertyNames = [NSMutableArray arrayWithCapacity:propertyCount];

    for (unsigned int i = 0; i < propertyCount; ++i) {
        objc_property_t property = properties[i];
        const char * name = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:name];
        [propertyNames addObject:propertyName];
    }
    free(properties);
    return propertyNames;
}

BOOL IsEqualObjects(id object1, id object2)
{
    if (object1 == object2)
        return YES;
    if (!object1 || ![object2 isKindOfClass:[object1 class]])
        return NO;

    NSArray *propertyNames = PropertyNamesFromObject(object1);
    for (NSString *propertyName in propertyNames) {
        if (([object1 valueForKey:propertyName] != [object2 valueForKey:propertyName])
            && (![[object1 valueForKey:propertyName] isEqual:[object2 valueForKey:propertyName]])) return NO;
    }

    return YES;
}

NSUInteger MagicHash(id object)
{
    NSUInteger prime = 31;
    NSUInteger result = 1;

    NSArray *propertyNames = PropertyNamesFromObject(object);

    for (NSString *propertyName in propertyNames) {
        id value = [object valueForKey:propertyName];
        result = prime * result + [value hash];
    }

    return result;
}

さて、あなたのカスタムクラスに簡単に実装することができますisEqual:hash

- (NSUInteger)hash
{
    return MagicHash(self);
}

- (BOOL)isEqual:(id)other
{
    return IsEqualObjects(self, other);
}

2

作成後に変更できるオブジェクトを作成している場合、オブジェクトがコレクションに挿入されてもハッシュ値は変更されないことに注意してください。実際には、これは、最初のオブジェクト作成の時点からハッシュ値を修正する必要があることを意味します。詳細については、NSObjectプロトコルの-hashメソッドに関するAppleのドキュメントを参照してください。

コレクション内のオブジェクトの位置を決定するためにハッシュ値を使用するコレクションに変更可能なオブジェクトが追加された場合、オブジェクトがコレクション内にある間、オブジェクトのハッシュメソッドによって返される値は変更できません。したがって、ハッシュメソッドがオブジェクトの内部状態情報に依存していないか、オブジェクトがコレクション内にあるときにオブジェクトの内部状態情報が変更されないようにする必要があります。したがって、たとえば、可変ディクショナリはハッシュテーブルに入れることができますが、そこにある間は変更しないでください。(特定のオブジェクトがコレクション内にあるかどうかを知るのは難しい場合があることに注意してください。)

潜在的にハッシュルックアップの効率が大幅に低下する可能性があるので、これは私にとって完全な改造のように聞こえますが、注意を怠って、ドキュメントの説明に従っている方がよいと思います。


1
あなたはハッシュのドキュメントを間違って読んでいます—それは本質的に「どちらか一方」の状況です。オブジェクトが変更されると、ハッシュも一般的に変更されます。これは実際にはプログラマーへの警告です。オブジェクトを変更した結果としてハッシュが変更された場合、ハッシュを利用するコレクションにあるオブジェクトを変更すると、予期しない動作が発生します。このような状況でオブジェクトを「安全に変更可能」にする必要がある場合は、ハッシュを変更可能な状態と無関係にするしかありません。その特定の状況は私には奇妙に聞こえますが、それが当てはまるまれな状況は確かにあります。
クインテイラー、

1

ここで完全なボフィンを鳴らす危険がある場合は申し訳ありませんが... ...「ベストプラクティス」に従うために誰も言及しなくても、ターゲットオブジェクトが所有するすべてのデータを考慮に入れないequalsメソッドを絶対に指定しないでください。データは、オブジェクトのアソシエートではなく、オブジェクトに集約されます。比較で「年齢」を考慮に入れたくない場合は、コンパレーターを作成し、isEqual:の代わりにそれを使用して比較を実行する必要があります。

等値比較を任意に実行するisEqual:メソッドを定義すると、等値解釈の「ひねり」を忘れると、このメソッドが別の開発者または自分自身によって誤用されるリスクが生じます。

エルゴ、これはハッシュに関する素晴らしいQ&Aですが、通常はハッシュ方法を再定義する必要はありません。代わりにアドホックコンパレータを定義する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.