Swift中如何定义含不同类型泛型属性实例的数组?
嗨,这个问题正好踩中了Swift泛型和Java通配符的核心差异——Swift确实没有像Java?那样的泛型通配符,但我们可以通过类型擦除的方式来实现你想要的效果:把不同E类型的Property<Test, E>统一放进同一个数组,同时完全隐藏E这个泛型参数,而且这个方案不依赖Hashable,可以通用到任何场景。
核心思路
我们需要把Property<T, E>中与E无关的公共操作抽象成一个协议,然后用一个“包装器”结构体来捕获具体Property的实现细节,对外只暴露T类型,从而擦除掉E的类型信息。
具体实现步骤
1. 定义抽象协议,剥离E类型
首先创建一个只关注核心操作的协议,不需要泛型参数E,只关联T:
public protocol AnyProperty<T> { associatedtype T // 你的核心操作:获取哈希值 func getMemberHashValue(instance: T) -> Int // 你的核心操作:比较属性值 func equalsMembers(instance1: T, instance2: T) -> Bool }
注:这里用了Swift 5.7+支持的关联类型协议泛型声明,让
T的关联更清晰,低版本可以把associatedtype T放在协议内部。
2. 让原Property结构体遵守协议
修改你的Property结构体,让它遵守上面的协议(其实你的原有方法已经完全符合协议要求,只需要加个协议声明就行):
public struct Property<T, E: Hashable>: AnyProperty { let getter: (T) -> E func getMemberHashValue(instance: T) -> Int { return getter(instance).hashValue } func equalsMembers(instance1: T, instance2: T) -> Bool { return getter(instance1) == getter(instance2) } }
3. 实现类型擦除的包装器
这个包装器是关键,它会把具体Property<T, E>的方法捕获到闭包里,对外只暴露T类型:
public struct AnyPropertyWrapper<T>: AnyProperty { // 用闭包捕获具体Property的实现 private let _getMemberHashValue: (T) -> Int private let _equalsMembers: (T, T) -> Bool // 初始化方法接受任意符合AnyProperty且关联类型为T的实例 init<U: AnyProperty>(_ property: U) where U.T == T { _getMemberHashValue = property.getMemberHashValue _equalsMembers = property.equalsMembers } // 实现协议方法,直接调用捕获的闭包 func getMemberHashValue(instance: T) -> Int { return _getMemberHashValue(instance) } func equalsMembers(instance1: T, instance2: T) -> Bool { return _equalsMembers(instance1, instance2) } }
4. 调整Test结构体的properties类型
现在你就可以把properties的类型指定为[AnyPropertyWrapper<Test>],不管原来的E是Int还是Double,都能被统一包装进去:
struct Test { let a: Int let b: Double let properties: [AnyPropertyWrapper<Test>] = [ AnyPropertyWrapper(Property(getter: { $0.a })), AnyPropertyWrapper(Property(getter: { $0.b })) ] }
方案的通用性说明
这个方案完全不依赖Hashable约束——如果你的场景不需要哈希操作,只需要比较,你可以:
- 去掉
Property中E: Hashable的约束 - 修改
AnyProperty协议,只保留equalsMembers方法 - 调整
AnyPropertyWrapper的闭包和实现
这种方式本质是把“具体类型的操作”转换成“闭包捕获的行为”,只要你能把需要的操作抽象到协议里,就能擦除掉不需要的泛型参数,完美替代Java通配符的场景。
对比AnyHashable的方案:用AnyHashable是把E转换成统一的类型,但会丢失类型信息,且只能用于Hashable场景;而类型擦除的方案保留了操作的语义,更灵活通用。
内容的提问来源于stack exchange,提问作者Feuermurmel

