注意:以下 SO 问题是相关的,但它们和链接的资源似乎都没有完全回答我的问题,特别是与实现对象集合的相等测试有关。
背景
NSObject 提供(返回实例的地址,如)和(除非接收者的地址和参数的地址相同,否则返回)的默认实现。这些方法被设计为在必要时被覆盖,但文档清楚地表明您应该提供两者或都不提供。此外,如果返回两个对象,则这些对象的结果必须相同。如果不是,那么当应该相同的对象(例如返回的两个字符串实例)被添加到 Cocoa 集合或直接比较时,就会出现问题。-hash
(NSUInteger)self
-isEqual:
NO
-isEqual:
YES
-hash
-compare:
NSOrderedSame
语境
我开发了 CHDataStructures.framework,这是一个 Objective-C 数据结构的开源库。我已经实现了许多集合,目前正在改进和增强它们的功能。我想添加的功能之一是能够将集合与另一个集合进行比较。
这些比较应该考虑两个集合中存在的对象(包括排序,如果适用),而不是只比较内存地址。这种方式在Cocoa中有相当的先例,一般采用单独的方式,包括以下几种:
-[NSArray isEqualToArray:]
-[NSDate isEqualToDate:]
-[NSDictionary isEqualToDictionary:]
-[NSNumber isEqualToNumber:]
-[NSSet isEqualToSet:]
-[NSString isEqualToString:]
-[NSValue isEqualToValue:]
我想让我的自定义集合对相等性测试具有鲁棒性,因此它们可以安全地(并且可预测地)添加到其他集合中,并允许其他集合(如 NSSet)确定两个集合是否相等/等价/重复。
问题
一个-isEqualTo...:
方法本身就可以很好地工作,但是如果参数与接收者属于同一类(或者可能是子类),或者其他情况下,定义这些方法的类通常也会覆盖-isEqual:
以调用。这意味着该类还必须定义为,它将为具有相同内容的不同实例返回相同的值。[self isEqualTo...:]
[super isEqual:]
-hash
此外,Apple 的文档-hash
规定如下:(强调我的)
“如果将可变对象添加到使用哈希值确定对象在集合中的位置的集合中,则该对象的哈希方法返回的值在对象在集合中时不得更改。因此,无论是哈希方法不得依赖任何对象的内部状态信息,或者您必须确保对象在集合中时对象的内部状态信息不会改变。因此,例如,可以将可变字典放入哈希表中,但您必须当它在那里时不要更改它。(请注意,很难知道给定对象是否在集合中。)“
编辑: 我完全理解为什么这是必要的,并且完全同意这个推理——我在这里提到它是为了提供额外的背景,并为了简洁而避开了为什么会这样的话题。
我所有的集合都是可变的,并且哈希必须至少考虑一些内容,所以这里唯一的选择是认为改变存储在另一个集合中的集合是一个编程错误。(我的收藏都采用了NSCopying,所以像 NSDictionary 这样的收藏可以成功地制作副本作为密钥等)
-isEqual:
实现and对我来说是有意义的-hash
,因为(例如)我的一个类的间接用户可能不知道-isEqualTo...:
要调用的具体方法,甚至不关心两个对象是否是同一个类的实例。他们应该能够调用-isEqual:
或调用-hash
任何类型的变量id
并获得预期的结果。
与-isEqual:
(可以访问被比较的两个实例)不同,-hash
必须“盲目地”返回结果,只能访问特定实例中的数据。由于它无法知道哈希的用途,因此对于所有应该被视为相等/相同的可能实例,结果必须一致,并且必须始终与. (编辑:这已经被下面的答案揭穿了,它确实让生活更轻松。)此外,编写好的散列函数并非易事——保证唯一性是一个挑战,尤其是当你只有一个 NSUInteger(32/64 位)时在其中代表它。-isEqual:
问题
- 为集合实施
相等比较时是否有最佳实践?-hash
- 在 Objective-C 和 Cocoa 风格的集合中是否有任何特殊需要计划?
-hash
是否有任何具有合理置信度的单元测试的好方法?- 关于实施
-hash
以同意-isEqual:
包含任意类型元素的集合的任何建议?我应该知道哪些陷阱?(编辑:不像我最初想的那样有问题——正如@kperryua指出的那样,“相等的-hash
值并不意味着-isEqual:
”。)
编辑: 我应该澄清一下,我对如何实现 -isEqual: 或 -isEqualTo...: 对于集合并不感到困惑,这很简单。我认为我的困惑主要源于(错误地)认为如果 -isEqual: 返回 NO,则 -hash 必须返回不同的值。过去做过密码学,我认为不同值的散列必须不同。然而,下面的答案让我意识到一个“好的”散列函数实际上是关于最小化桶冲突和链接使用-hash
. 虽然唯一的哈希是可取的,但它们并不是严格的要求。