3

に 2 つのオブジェクトが追加されましたNSSetが、メンバーシップを確認すると、そのうちの 1 つが見つかりません。

以下のテスト コードは、iOS7 では問題なく動作しましたが、iOS8 では失敗します。

SKNode *changingNode = [SKNode node];
SKNode *unchangingNode = [SKNode node];
NSSet *nodes = [NSSet setWithObjects:unchangingNode, changingNode, nil];

changingNode.position = CGPointMake(1.0f, 1.0f);

if ([nodes containsObject:changingNode]) {
  printf("found node\n");
} else {
  printf("could not find node\n");
}

出力:

ノードが見つかりませんでした

iOS7 と iOS8 の間で何が起こったのか、どうすれば修正できますか?

4

1 に答える 1

4

SKNodeisEqualのおよびの実装はhash、iOS8 で変更され、オブジェクトのデータ メンバーが含まれるようになりました (オブジェクトのメモリ アドレスだけではありません)。

コレクションに関するApple のドキュメントでは、この正確な状況について次のように警告しています。

変更可能なオブジェクトがセットに格納されている場合、オブジェクトのハッシュ メソッドが変更可能なオブジェクトの内部状態に依存してはならないか、または変更可能なオブジェクトがセット内にある間は変更されるべきではありません。たとえば、変更可能な辞書をセットに入れることができますが、そこにある間は変更してはなりません。

そして、より直接的に、ここに

変更可能なオブジェクトをコレクション オブジェクトに格納すると、問題が発生する可能性があります。コレクションに含まれるオブジェクトが変化すると、特定のコレクションが無効になったり、破損したりする可能性があります。

一般的な状況は、他の質問で詳しく説明されています。ただし、SKNodeiOS8 へのアップグレードでこの問題を発見した方の助けになることを願って、例の説明を繰り返します。

この例では、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]あり、オブジェクトが保持されたポインターである場所を使用します。これにより、次のことが可能になります。オブジェクトが変更された場合でも有効なオブジェクトをすばやく検索できます。迅速な削除; コレクションに入れられたオブジェクトの保持。

  • 最後のものと同様に、異なるセマンティクスとその他の便利なオプションを使用します: NSMapTablewith オプションを使用しNSMapTableObjectPointerPersonalityて、キー オブジェクトがハッシュと等価のポインターとして扱われるようにします。

于 2014-10-01T14:40:07.093 に答える