SKNode
的实现在 iOS8isEqual
中hash
已更改为包含对象的数据成员(而不仅仅是对象的内存地址)。
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
key 是 the[NSValue valueWithPointer]
并且 object 是保留指针的地方。这给了我: 快速查找即使对象更改也有效的对象;快速删除;并保留收藏中的物品。
与上一个类似,具有不同的语义和一些其他有用的选项:使用NSMapTable
with 选项NSMapTableObjectPointerPersonality
,以便将关键对象视为哈希和相等的指针。