两个对象被添加到NSSet
,但是当我检查成员资格时,我找不到其中一个。
以下测试代码在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之间发生了什么,我该如何解决?
答案 0 :(得分:4)
SKNode
的{{1}}和isEqual
的实现在iOS8中已更改为包含对象的数据成员(而不仅仅是对象的内存地址)。
Apple documentation for collections警告这种情况:
如果可变对象存储在一个集合中,那么就是哈希方法 对象不应该依赖于可变对象的内部状态 或者可变对象在它们处于集合中时不应被修改。 例如,可变字典可以放在一个集合中,但您必须 当它在那里时不要改变它。
而且,更直接地,here:
在集合对象中存储可变对象可能会导致问题。 某些集合可能会变得无效甚至损坏它们的对象 包含mutate,因为通过变异,这些对象可以影响方式 它们被放置在集合中。
描述了一般情况in other questions in detail。但是,我将重复hash
示例的解释,希望通过升级到iOS8帮助那些发现此问题的人。
在示例中,SKNode
对象SKNode
被插入changingNode
(使用哈希表实现)。计算对象的哈希值,并在哈希表中为其分配一个桶:让我们说桶1。
NSSet
输出:
指针790756a0哈希838599421
然后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];
被修改。修改导致更改对象的哈希值。 (在iOS7中,更改像这样的对象 not 更改其哈希值。)
changingNode
输出:
指针790756a0 hash 3025143289
现在调用changingNode.position = CGPointMake(1.0f, 1.0f);
printf("pointer %lx hash %lu\n", (uintptr_t)changingNode, (unsigned long)changingNode.hash);
时,计算的哈希值(可能)被分配给不同的桶:比如桶2.使用containsObject
将桶2中的所有对象与测试对象进行比较,但是当然都归还NO。
在现实生活中的例子中,对isEqual
的修改可能发生在其他地方。如果您尝试在changedObject
调用的位置进行调试,您可能会发现该集合包含的对象具有与查找对象完全相同的地址和哈希值,但查找失败。
替代实施(每个都有各自的问题)
仅在集合中使用不变的对象。
现在,只有在完全控制时才将对象放入集合中
永远地,他们对containsObject
和isEqual
。
跟踪一组(非保留)指针而不是一组对象:hash
使用其他收藏品。例如,[NSSet setWithObject:[NSValue valueWithPointer:(void *)changingNode]]
将受到更改的影响
NSArray
,但不会受isEqual
更改的影响。 (当然,如果
您尝试保持数组排序以便更快地查找,您将拥有
类似的问题。)
这通常是我现实世界中最好的选择:使用hash
,其中键是NSDictionary
,对象是保留的指针。这让我:快速查找即使对象发生变化也会有效的对象;快速删除;和保留放在集合中的对象。
与上一个类似,具有不同的语义和一些其他有用的选项:使用带有选项[NSValue valueWithPointer]
的{{1}},以便将关键对象视为散列和相等的指针。