如何创建类似Set的Identifiable对象集合?解决Hashable重复问题
解决Identifiable对象集合的重复与更新问题
这个问题确实戳中了Swift里用可变Identifiable对象管理集合的痛点——Set依赖Hashable协议,但可变属性的变更会导致哈希值改变,进而出现重复项,而我们真正想要的是用id作为唯一标识来管理集合,对吧?下面给你几个实用的解决方案:
方案1:用字典以ID为Key存储
最直接的办法就是用字典,把Identifiable的id作为字典的Key,对象本身作为Value。这样不管对象的其他属性怎么变,只要id不变,更新时就会直接覆盖旧值,完全不会有重复问题:
struct Parcel: Identifiable { let id: String var location: Int? } // 初始化字典 var item = Parcel(id: "123") var parcelDict: [String: Parcel] = [item.id: item] // 修改属性后更新字典 item.location = 33435 parcelDict[item.id] = item
字典的查找、插入、更新操作都是O(1)复杂度,和Set的效率差不多,而且逻辑非常直观。
方案2:自定义基于Identifiable的集合类型
如果你更习惯Set的使用方式,可以自己封装一个专门针对Identifiable对象的集合,内部用字典做存储,对外暴露类似Set的接口:
struct IdentifiableSet<Element: Identifiable> { private var storage: [Element.ID: Element] = [:] // 插入元素(自动覆盖同ID的旧元素) mutating func insert(_ element: Element) { storage[element.id] = element } // 更新元素(和insert逻辑一致,语义更清晰) mutating func update(with element: Element) { storage[element.id] = element } // 判断是否包含某个元素(基于ID) func contains(_ element: Element) -> Bool { storage.keys.contains(element.id) } // 按需扩展其他方法,比如获取所有元素、移除元素等 var elements: [Element] { Array(storage.values) } } // 使用示例 var item = Parcel(id: "123") var parcelSet = IdentifiableSet<Parcel>() parcelSet.insert(item) item.location = 33435 parcelSet.update(with: item) // 此时集合里只有一个元素,不会出现重复
这个方案既保留了集合的使用习惯,又完全基于id来管理元素,完美避开了Hashable带来的可变属性问题。
方案3:修改Hashable实现,仅基于ID计算哈希(谨慎使用)
如果一定要用原生Set,可以重写Hashable的实现,让哈希值和相等判断只依赖id,忽略其他可变属性:
struct Parcel: Identifiable, Hashable { let id: String var location: Int? // 只将id加入哈希计算 func hash(into hasher: inout Hasher) { hasher.combine(id) } // 仅比较id判断是否相等 static func == (lhs: Parcel, rhs: Parcel) -> Bool { lhs.id == rhs.id } }
这样修改后,就算location变更,item的哈希值也不会变,调用list.update(with: item)时就能正确找到原对象并更新,不会添加重复项。不过要注意:这样的Hashable实现意味着“只要id相同就是同一个对象”,你需要确保这符合你的业务逻辑——如果业务上允许两个id相同但属性不同的对象存在,这个方案就不适用。
内容的提问来源于stack exchange,提问作者TruMan1
相关产品推荐
相关产品推荐

