两个对象被添加到 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 之间发生了什么,我该如何解决?
最佳答案
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];
输出:
然后修改
changingNode
。修改会导致对象的哈希值发生更改。 (在 iOS7 中,像这样改变对象并没有改变它的哈希值。)changingNode.position = CGPointMake(1.0f, 1.0f);
printf("pointer %lx hash %lu\n", (uintptr_t)changingNode, (unsigned long)changingNode.hash);
输出:
现在,当调用
containsObject
时,计算出的哈希值(可能)分配给不同的存储桶:比如存储桶 2。存储桶 2 中的所有对象都使用 isEqual
与测试对象进行比较,但当然都返回 NO。在现实生活中,对
changedObject
的修改可能发生在其他地方。如果您尝试在 containsObject
调用的位置进行调试,您可能会困惑地发现该集合包含一个与查找对象具有完全相同地址和哈希值的对象,但查找失败。替代实现 (每个都有自己的问题)
并且永远,在他们的
isEqual
和 hash
实现上。 [NSSet setWithObject:[NSValue valueWithPointer:(void *)changingNode]]
NSArray
将受到更改的影响isEqual
但不会受到 hash
更改的影响。 (当然,如果您尝试对数组进行排序以加快查找速度,您将拥有
类似的问题。)
NSDictionary
,其中键是 [NSValue valueWithPointer]
,对象是保留的指针。这给了我:快速查找即使对象更改也有效的对象;快速删除;并保留放入集合中的对象。 NSMapTable
的 NSMapTableObjectPointerPersonality
,以便将关键对象视为散列和相等的指针。 关于ios8 - 为什么 [NSSet containsObject] 在 iOS8 中对 SKNode 成员失败?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26143970/