【发布时间】:2015-10-24 15:37:56
【问题描述】:
在 Swift 中继承 NSObject 时,应该覆盖 hash 还是实现 Hashable?
另外,您应该覆盖isEqual: 还是实现== 运算符?
【问题讨论】:
在 Swift 中继承 NSObject 时,应该覆盖 hash 还是实现 Hashable?
另外,您应该覆盖isEqual: 还是实现== 运算符?
【问题讨论】:
NSObject 已经符合Hashable 协议:
extension NSObject : Equatable, Hashable {
/// The hash value.
///
/// **Axiom:** `x == y` implies `x.hashValue == y.hashValue`
///
/// - Note: the hash value is not guaranteed to be stable across
/// different invocations of the same program. Do not persist the
/// hash value across program runs.
public var hashValue: Int { get }
}
public func ==(lhs: NSObject, rhs: NSObject) -> Bool
我找不到官方参考,但似乎hashValue
从NSObjectProtocol 调用hash 方法,== 调用
isEqual: 方法(来自同一协议)。 查看更新
答案结束!
对于NSObject 子类,正确的方法似乎是
覆盖hash 和isEqual:,这是一个实验
证明:
hashValue 和 ==
class ClassA : NSObject {
let value : Int
init(value : Int) {
self.value = value
super.init()
}
override var hashValue : Int {
return value
}
}
func ==(lhs: ClassA, rhs: ClassA) -> Bool {
return lhs.value == rhs.value
}
现在创建两个不同的类实例 “相等”并将它们放入一个集合中:
let a1 = ClassA(value: 13)
let a2 = ClassA(value: 13)
let nsSetA = NSSet(objects: a1, a2)
let swSetA = Set([a1, a2])
print(nsSetA.count) // 2
print(swSetA.count) // 2
如您所见,NSSet 和 Set 都将对象视为不同的对象。
这不是预期的结果。数组也有意想不到的结果:
let nsArrayA = NSArray(object: a1)
let swArrayA = [a1]
print(nsArrayA.indexOfObject(a2)) // 9223372036854775807 == NSNotFound
print(swArrayA.indexOf(a2)) // nil
设置断点或添加调试输出表明被覆盖的
== 运算符永远不会被调用。不知道这是bug还是
预期的行为。
hash 和 isEqual:
class ClassB : NSObject {
let value : Int
init(value : Int) {
self.value = value
super.init()
}
override var hash : Int {
return value
}
override func isEqual(object: AnyObject?) -> Bool {
if let other = object as? ClassB {
return self.value == other.value
} else {
return false
}
}
}
对于 Swift 3,isEqual: 的定义更改为
override func isEqual(_ object: Any?) -> Bool { ... }
现在所有结果都符合预期:
let b1 = ClassB(value: 13)
let b2 = ClassB(value: 13)
let nsSetB = NSSet(objects: b1, b2)
let swSetB = Set([b1, b2])
print(swSetB.count) // 1
print(nsSetB.count) // 1
let nsArrayB = NSArray(object: b1)
let swArrayB = [b1]
print(nsArrayB.indexOfObject(b2)) // 0
print(swArrayB.indexOf(b2)) // Optional(0)
更新:该行为记录在“Using Swift with Cocoa and Objective-C”一书中的“Interacting with Objective-C API”下:
==运算符的默认实现调用isEqual:方法,===运算符的默认实现检查指针是否相等。对于从 Objective-C 导入的类型,您不应覆盖相等或恒等运算符。
NSObject类提供的isEqual:的基本实现等价于指针相等的身份检查。您可以在子类中覆盖isEqual:,让 Swift 和 Objective-C API 根据对象的内容而不是它们的身份来确定相等性。
这本书在 Apple Book 应用中可用。
它也记录在 Apple 的网站上,但已被删除,并且仍然在页面的 WebArchive snapshot 上可见。
【讨论】:
-hash,而只需覆盖-isEqual:。显然,对于所有可变类,-hash 没有很好的实现。唯一可能的实现是返回一个常量(即类对象的地址或一个幻数。)此外,在集合中具有可变类是不可能满足使用集合的期望(没有两个实例是相等的)。这是核心数据中公平和身份一致的原因之一。
hash 和 -isEqual:
对于NSObject,最好覆盖hash 和isEqual。它已经符合Hashable 和Equatable 并且已经为依次调用hash 和isEqual 合成了一致性。所以既然是NSObject,就用ObjC的方式,覆盖也会影响ObjC哈希值和相等性的值。
class Identity: NSObject {
let name: String
let email: String
init(name: String, email: String) {
self.name = name
self.email = email
}
override var hash: Int {
var hasher = Hasher()
hasher.combine(name)
hasher.combine(email)
return hasher.finalize()
}
override func isEqual(_ object: Any?) -> Bool {
guard let other = object as? Identity else {
return false
}
return name == other.name && email == other.email
}
}
【讨论】:
“另外,你应该重写 isEqual: 还是实现 ==?”
两者都可以。如果你要实现 行为不同,您也会为代码用户的生活增添光彩。去过也做过。很有趣。
【讨论】:
实现Hashable,这还需要您为您的类型实现== 运算符。这些用于 Swift 标准库中的许多有用的东西,例如 indexOf 函数,它只适用于实现 Equatable 的类型的集合,或者 Set<T> 类型,它只适用于实现 Hashable 的类型.
【讨论】: