SKNode
的isEqual
和hash
的實現已在iOS8中更改爲包含對象的數據成員(而不僅僅是對象的內存地址)。
的Apple documentation for collections警告有關此確切的情況:
如果可變對象被存儲在一組,無論是 對象的散列法不應該依賴於可變對象 或的內部狀態可變對象在集合中不應該被修改。 例如,一個可變字典可以放在一個集合中,但是您必須在其中存在時不改變它。
而且,更直接,here:
存儲在集合對象的可變對象可能會出現問題。 如果對象 包含變異,某些集合可能會變得無效甚至損壞,因爲通過變異這些對象可能會影響它們放置在集合中的方式 。
一般情況描述爲in other questions in detail。不過,我會重複對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
和選項NSMapTableObjectPointerPersonality
,以便將關鍵對象視爲散列和相等的指針。