Swift:如何让含关联类型的any SomeProtocol遵循Hashable等协议?
解决「Type 'any SomeProtocol' cannot conform to 'Hashable'」错误
要让带关联类型的any SomeProtocol遵循Hashable/Equatable,核心是处理类型擦除后的协议实例的相等性和哈希逻辑,以下两种方案按需选择:
方案一:类型擦除包装器(推荐,支持关联类型参与判断)
这种方式通过包装器统一处理不同关联类型实例的Hashable逻辑,适合需要根据关联类型属性(比如v)判断相等性的场景。
步骤1:修改协议并添加默认实现
让协议继承Hashable,同时给关联类型T加上Hashable约束,并提供默认的相等性和哈希实现:
protocol SomeProtocol: Hashable { associatedtype T: Hashable var v: T { get set } var uuid: String { get set } } extension SomeProtocol { // 默认基于uuid和v判断相等性 static func == (lhs: Self, rhs: Self) -> Bool { lhs.uuid == rhs.uuid && lhs.v == rhs.v } // 默认将uuid和v加入哈希 func hash(into hasher: inout Hasher) { hasher.combine(uuid) hasher.combine(v) } }
步骤2:创建类型擦除包装器
struct AnySomeProtocol: Hashable { private let base: any SomeProtocol private let equals: (AnySomeProtocol) -> Bool private let hash: (inout Hasher) -> Void init<T: SomeProtocol>(_ base: T) { self.base = base // 仅当类型相同时才比较具体属性 self.equals = { other in guard let otherBase = other.base as? T else { return false } return base == otherBase } self.hash = { hasher in base.hash(into: &hasher) } } // 暴露原协议的公共属性(按需添加) var uuid: String { base.uuid } var v: Any { base.v } } // 实现Hashable协议要求 extension AnySomeProtocol { static func == (lhs: AnySomeProtocol, rhs: AnySomeProtocol) -> Bool { lhs.equals(rhs) } func hash(into hasher: inout Hasher) { hash(&hasher) } }
步骤3:修改业务代码
用AnySomeProtocol包装实例后存入数组和集合:
struct S1: SomeProtocol { var v: Int var uuid: String = "S1" } struct S2: SomeProtocol { var v: String var uuid: String = "S2" } var a: [AnySomeProtocol] = [] a.append(AnySomeProtocol(S1(v: 10))) a.append(AnySomeProtocol(S1(v: 15))) a.append(AnySomeProtocol(S2(v: "hello"))) var b = Set(a) // 编译正常
方案二:直接扩展存在类型(Swift 5.7+,仅适用于无关联类型依赖的场景)
如果你的相等性判断只依赖协议中无关联类型的属性(比如uuid),可以直接给any SomeProtocol扩展Hashable实现,代码更简洁:
步骤1:修改协议并扩展存在类型
protocol SomeProtocol { associatedtype T var v: T { get set } var uuid: String { get set } } // 让any SomeProtocol直接遵循Hashable extension any SomeProtocol: Hashable { static func == (lhs: Self, rhs: Self) -> Bool { lhs.uuid == rhs.uuid // 仅基于uuid判断相等性 } func hash(into hasher: inout Hasher) { hasher.combine(uuid) } }
步骤2:原业务代码直接使用
不需要修改数组和集合的定义,原代码即可编译:
struct S1: SomeProtocol { var v: Int var uuid: String = "S1" } struct S2: SomeProtocol { var v: String var uuid: String = "S2" } var a: [any SomeProtocol] = [] a.append(S1(v: 10)) a.append(S1(v: 15)) a.append(S2(v: "hello")) var b = Set(a) // 编译正常
注意事项
- 方案二中,如果不同实例的
uuid重复,会被Set视为同一个元素,需确保uuid的唯一性。 - 类型擦除包装器中,如果需要访问原实例的特定方法,可以在
AnySomeProtocol中添加对应的泛型方法或类型转换逻辑。
内容的提问来源于stack exchange,提问作者Oven.V
相关产品推荐
相关产品推荐

