使用Swift Set存储Hashable对象的异常问题及解决办法
用Set跟踪带唯一键的可变对象的正确姿势
要通过Set跟踪拥有唯一键但数据可变的对象时,Set要求元素必须遵循Hashable协议,但如果只实现基于键的哈希方法,会出现同键对象重复存入的问题。
错误示例:同键对象重复存入
只实现Hashable的哈希方法,仅用key计算哈希值,但未自定义相等判断逻辑时,update(with:)无法正确替换同键条目:
struct Value: Hashable { let key: Int var contents: Int func hash(into hasher: inout Hasher) { hasher.combine(key) } } var set = Set<Value>() for key in 1...5 { set.insert(Value(key: key, contents: key)) } set.update(with: Value(key: 1, contents: 1000)) print("Set contains key 1, value 1. == \(set.contains(Value(key:1, contents: 1)))") print("Set contains key 1, value 1000. == \(set.contains(Value(key:1, contents: 1000)))") set.forEach { print($0, $0.hashValue) }
运行后会发现Set中存在两个key=1的对象,update(with:)并未替换原有条目,反而新增了重复项。
问题根源与解决方法
Set的底层逻辑是:先通过哈希值定位元素位置,再用==判断元素是否相等。如果只实现哈希方法,Swift会自动生成基于所有属性的==实现,导致同键但contents不同的对象被判定为不相等,因此无法触发替换逻辑。
正确的做法是同时遵循Hashable和Equatable协议,自定义==方法,仅基于key判断对象相等:
struct Value: Hashable, Equatable { let key: Int var contents: Int func hash(into hasher: inout Hasher) { hasher.combine(key) } static func == (lhs: Value, rhs: Value) -> Bool { lhs.key == rhs.key } } var set = Set<Value>() for key in 1...5 { set.insert(Value(key: key, contents: key)) } set.update(with: Value(key: 1, contents: 1000)) print("Set contains key 1, value 12345 == \(set.contains(Value(key:1, contents: 12345))) (contents ignored)") // contents ignored set.forEach { print($0, $0.hashValue) }
此时update(with:)会正确替换同键条目,Set中不会出现重复的key对象,contains判断也会忽略contents的差异,仅以key为准。
内容的提问来源于stack exchange,提问作者Duncan C
相关产品推荐
相关产品推荐

