SKNode
isEqual
のおよびの実装はhash
、iOS8 で変更され、オブジェクトのデータ メンバーが含まれるようになりました (オブジェクトのメモリ アドレスだけではありません)。
コレクションに関するApple のドキュメントでは、この正確な状況について次のように警告しています。
変更可能なオブジェクトがセットに格納されている場合、オブジェクトのハッシュ メソッドが変更可能なオブジェクトの内部状態に依存してはならないか、または変更可能なオブジェクトがセット内にある間は変更されるべきではありません。たとえば、変更可能な辞書をセットに入れることができますが、そこにある間は変更してはなりません。
そして、より直接的に、ここに:
変更可能なオブジェクトをコレクション オブジェクトに格納すると、問題が発生する可能性があります。コレクションに含まれるオブジェクトが変化すると、特定のコレクションが無効になったり、破損したりする可能性があります。
一般的な状況は、他の質問で詳しく説明されています。ただし、SKNode
iOS8 へのアップグレードでこの問題を発見した方の助けになることを願って、例の説明を繰り返します。
この例では、SKNode
オブジェクトは(ハッシュ テーブルを使用して実装changingNode
された) に挿入されます。NSSet
オブジェクトのハッシュ値が計算され、ハッシュ テーブルのバケットが割り当てられます。たとえば、バケット 1 とします。
SKNode *changingNode = [SKNode node];
SKNode *unchangingNode = [SKNode node];
printf("pointer %lx hash %lu\n", (uintptr_t)changingNode, (unsigned long)changingNode.hash);
NSSet *nodes = [NSSet setWithObjects:unchangingNode, changingNode, nil];
出力:
ポインター 790756a0 ハッシュ 838599421
その後changingNode
、変更されます。変更により、オブジェクトのハッシュ値が変更されます。(iOS7 では、このようにオブジェクトを変更してもハッシュ値は変更されませんでした。)
changingNode.position = CGPointMake(1.0f, 1.0f);
printf("pointer %lx hash %lu\n", (uintptr_t)changingNode, (unsigned long)changingNode.hash);
出力:
ポインター 790756a0 ハッシュ 3025143289
が呼び出されると、計算されたハッシュ値が (おそらく) 別のバケットに割り当てられますcontainsObject
。たとえば、バケット 2 です。バケット 2 のすべてのオブジェクトは、 を使用してテスト オブジェクトと比較されますisEqual
が、もちろんすべて NO が返されます。
実際の例では、変更はchangedObject
おそらく別の場所で行われます。呼び出しの場所でデバッグしようとするとcontainsObject
、検索オブジェクトとまったく同じアドレスとハッシュ値を持つオブジェクトがコレクションに含まれていることに混乱する可能性がありますが、検索は失敗します。
代替実装(それぞれに独自の問題があります)
コレクションでは不変のオブジェクトのみを使用してください。
isEqual
およびの実装を完全に制御できる場合にのみ、オブジェクトをコレクションに入れますhash
。
オブジェクトのセットではなく、(保持されていない) ポインターのセットを追跡します。[NSSet setWithObject:[NSValue valueWithPointer:(void *)changingNode]]
別のコレクションを使用してください。たとえば、NSArray
は への変更によって影響を受けますが、 への変更
isEqual
によっては影響を受けませんhash
。(もちろん、検索を高速化するために配列をソートしたままにしておくと、同様の問題が発生します。)
多くの場合、これは私の実世界の状況に最適な代替手段です。NSDictionary
キーが で[NSValue valueWithPointer]
あり、オブジェクトが保持されたポインターである場所を使用します。これにより、次のことが可能になります。オブジェクトが変更された場合でも有効なオブジェクトをすばやく検索できます。迅速な削除; コレクションに入れられたオブジェクトの保持。
最後のものと同様に、異なるセマンティクスとその他の便利なオプションを使用します: NSMapTable
with オプションを使用しNSMapTableObjectPointerPersonality
て、キー オブジェクトがハッシュと等価のポインターとして扱われるようにします。