如何为所有Hashable类型添加UUID以实现元素唯一性?
为遵循Hashable协议的类型添加唯一UUID标识
问题场景
你有一个包含任意遵循Hashable协议类型的数组,比如字符串数组:
let people = ["Joe", "Tom", "Bob", "Joe"] if people[3] == people[people.firstIndex(of: "Joe")!] { print(true) // 始终返回true }
Hashable的hashValue是基于值计算的,相同值的元素哈希值一致,导致无法通过哈希或相等性判断区分值相同但逻辑上独立的元素。你希望让所有遵循Hashable的类型都拥有唯一的id: UUID属性,实现实例级的唯一性。
直接扩展Hashable不可行的原因
直接给Hashable协议扩展添加UUID属性达不到目的,核心问题在于:
- Hashable协议同时支持值类型(如String、Int、结构体)和引用类型(如类)
- 对于值类型来说,实例在复制时会拷贝所有属性。比如给String扩展UUID后,创建第二个"Joe"时,它的UUID会和第一个"Joe"完全一致,无法实现唯一性。
可行解决方案:创建包装器类型
最通用的方案是自定义一个遵循Hashable的包装器,将原始类型和唯一UUID绑定,同时保留原始类型的Hashable特性:
struct UniqueWrapper<T: Hashable>: Hashable { let value: T let id = UUID() // 可选:如果需要保留原始类型的相等性判断(即value相同则实例相等),自定义如下实现 static func == (lhs: UniqueWrapper<T>, rhs: UniqueWrapper<T>) -> Bool { return lhs.value == rhs.value } func hash(into hasher: inout Hasher) { hasher.combine(value) } // 如果不需要保留原始相等性,直接使用Swift自动合成的==和hash(into:)即可,此时id会参与哈希和相等判断 }
使用示例
// 创建包装后的数组 let wrappedPeople: [UniqueWrapper<String>] = [ .init(value: "Joe"), .init(value: "Tom"), .init(value: "Bob"), .init(value: "Joe") ] // 两个"Joe"的UUID完全不同 print(wrappedPeople[0].id == wrappedPeople[3].id) // 输出false // 如果自定义了==基于value,那么实例相等性依然保留 print(wrappedPeople[0] == wrappedPeople[3]) // 输出true
针对引用类型的特殊处理
如果你的Hashable类型是引用类型(类),可以直接扩展Hashable添加UUID属性,因为引用类型的实例是唯一的,UUID只会在实例初始化时生成一次:
extension Hashable where Self: AnyObject { var id: UUID { if let existingId = objc_getAssociatedObject(self, &AssociatedKeys.id) as? UUID { return existingId } let newId = UUID() objc_setAssociatedObject(self, &AssociatedKeys.id, newId, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return newId } } private enum AssociatedKeys { static var id = "id" }
但这个方案只适用于类类型,无法覆盖所有Hashable类型,因此包装器方案仍是通用首选。
内容的提问来源于stack exchange,提问作者Joe Scotto
相关产品推荐
相关产品推荐

