我在考虑Swift如何确保Set的唯一性,因为我免费将obj的一个从Equatable
转换为Hashable
,所以我想到了这个简单的游乐场
struct SimpleStruct: Hashable {
let string: String
let number: Int
static func == (lhs: SimpleStruct, rhs: SimpleStruct) -> Bool {
let areEqual = lhs.string == rhs.string
print(lhs, rhs, areEqual)
return areEqual
}
}
var set = Set<SimpleStruct>()
let first = SimpleStruct(string: "a", number: 2)
set.insert(first)
所以我的第一个问题是:
每次在集合中插入新的obj时,都会调用static func ==
方法吗?
我的问题来自这个想法:
对于Equatable
obj,要做出此决定,确保两个obj相同的唯一方法是询问static func ==
的结果。
对于Hashable
obj,一种更快的方法是比较hashValue
……但是,就像我的情况一样,默认实现将同时使用string
和number
,与==
逻辑相反。
因此,为了测试Set
的行为,我刚刚添加了一条打印语句。
我发现有时我会收到打印声明,有时没有。有时hashValue
不足以做出此决定...因此,并非每次都调用此方法。
奇怪...
因此,我尝试添加两个相等的对象,并想知道set.contains
的结果是什么
let second = SimpleStruct(string: "a", number: 3)
print(first == second) // returns true
set.contains(second)
奇迹,在操场上发射了几次,我得到了不同的结果,这可能会导致不可预测的结果... 添加
var hashValue: Int {
return string.hashValue
}
它消除了任何意外的结果,但我的疑问是:
为什么在没有自定义hashValue
实现的情况下,有时会调用==
而有时却没有调用?
苹果应该避免这种意外行为吗?
答案 0 :(得分:1)
Hashable
要求的综合实现使用所有存储的
struct
(在您的情况下为string
和number
的属性中)。您的实施
==
的值仅基于字符串:
let first = SimpleStruct(string: "a", number: 2)
let second = SimpleStruct(string: "a", number: 3)
print(first == second) // true
print(first.hashValue == second.hashValue) // false
这违反了Hashable
协议的要求:
两个相等的实例必须以相同的顺序将相同的值提供给hash(into :)中的Hasher。
并导致未定义的行为。 (由于散列值是随机的 自Swift 4.2起,每个程序运行的行为都可能不同。)
测试中可能发生的 是,哈希值second
用于确定该值所在的集合的“桶”
将被存储。这可能与存储first
的存储桶不同。 –但这是一个实现细节:未定义行为是未定义行为,它可能导致意外结果甚至
运行时错误。
实施
var hashValue: Int {
return string.hashValue
}
或者(从Swift 4.2开始)
func hash(into hasher: inout Hasher) {
hasher.combine(string)
}
修复了违反规则的情况,因此使您的代码表现出预期的效果。