非final Dimension子类遵循ConvertibleUnit协议的Swift实现问题
我正在构建一款计量单位转换APP,核心是封装Foundation的Measurement系统。为了获取Dimension各子类的所有可用单位,我定义了以下协议和配套结构:
protocol ConvertibleUnit: Dimension { associatedtype AllUnits: Collection<UnitDescriptor<Self>> = [UnitDescriptor<Self>] static func allUnits(per coefficient: Decimal?) -> AllUnits } struct UnitDescriptor<Convertible: ConvertibleUnit> { let name: String let convertibleUnit: Convertible }
这个协议类似增强版的CaseIterable,但在让非final类(比如UnitAcceleration)遵循时会出现警告:
extension UnitAcceleration: ConvertibleUnit { // WARNING: Non-final class 'UnitAcceleration' cannot safely conform to protocol 'ConvertibleUnit', which requires that 'Self.AllUnits.Element' is exactly equal to 'UnitDescriptor<Self>'; this is an error in Swift 6 static func allUnits(per coefficient: Decimal?) -> [UnitDescriptor<UnitAcceleration>] { return [ UnitDescriptor(name: String(localized: "unit.acceleration.meters-per-second-squared.name"), convertibleUnit: .metersPerSecondSquared), UnitDescriptor(name: String(localized: "unit.acceleration.gravity.name"), convertibleUnit: .gravity) ] } }
我清楚问题出在非final类无法保证子类不会新增单位,协议要求严格的类型匹配。但改用final子类时,又会遇到父类工厂方法返回类型不匹配的错误:
final class ConvertibleUnitAcceleration: UnitAcceleration, ConvertibleUnit { static func allUnits(per coefficient: Decimal?) -> [UnitDescriptor<ConvertibleUnitAcceleration>] { return [ UnitDescriptor(name: String(localized: "unit.acceleration.meters-per-second-squared.name"), convertibleUnit: .metersPerSecondSquared), // ERROR: Member 'metersPerSecondSquared' in 'ConvertibleUnitAcceleration' produces result of type 'UnitAcceleration', but context expects 'ConvertibleUnitAcceleration' UnitDescriptor(name: String(localized: "unit.acceleration.gravity.name"), convertibleUnit: .gravity) // ERROR: Member 'gravity' in 'ConvertibleUnitAcceleration' produces result of type 'UnitAcceleration', but context expects 'ConvertibleUnitAcceleration' ] } }
原因是UnitAcceleration的静态属性返回的是UnitAcceleration类型,而非子类Self类型。我尝试过定义继承自Self的关联类型,但Swift不允许:
protocol ConvertibleUnit: Dimension { associatedtype SpecificUnit: Self // ERROR: Type 'Self.SpecificUnit' constrained to non-protocol, non-class type 'Self' associatedtype AllUnits: Collection<UnitDescriptor<SpecificUnit>> = [UnitDescriptor<Self>] static func allUnits(per coefficient: Decimal?) -> AllUnits }
目前唯一可行的方案是把关联类型约束改为ConvertibleUnit,但这会允许返回其他类型(比如UnitDistance),不符合需求。请问如何正确定义协议,让Dimension子类能安全遵循并标准化枚举所有单位?
解决方案:调整协议关联类型,结合类型转换与扩展
1. 修改协议结构,优化类型约束
重新定义协议,新增UnitType关联类型来约束单位的具体维度,同时放宽UnitDescriptor的类型限制,避免非final类的类型安全警告:
protocol ConvertibleUnit: Dimension { // 约束单位必须属于当前维度或其子类 associatedtype UnitType: Dimension = Self // 集合元素类型改为对应维度的UnitDescriptor associatedtype AllUnits: Collection<UnitDescriptor<UnitType>> = [UnitDescriptor<UnitType>] static func allUnits(per coefficient: Decimal?) -> AllUnits } // 调整结构体,将泛型约束改为Dimension,适配更广泛的维度类型 struct UnitDescriptor<Unit: Dimension> { let name: String let unit: Unit }
2. 非final Dimension子类的协议实现
对于Foundation原生的非final类(如UnitAcceleration),直接通过扩展遵循协议即可,因为原生静态属性本身就是对应类型,无需额外转换:
extension UnitAcceleration: ConvertibleUnit { static func allUnits(per coefficient: Decimal?) -> [UnitDescriptor<UnitAcceleration>] { // 基础单位列表,可根据coefficient参数过滤 let baseUnits: [UnitAcceleration] = [.metersPerSecondSquared, .gravity] return baseUnits.map { UnitDescriptor( name: String(localized: "unit.acceleration.\($0.symbol.lowercased()).name"), unit: $0 ) } } }
3. final子类的类型适配方案
如果需要自定义final子类,有两种方式解决父类静态属性的类型不匹配问题:
方式一:安全强制类型转换
在协议实现中,将父类的单位实例强制转换为子类类型(确保逻辑安全的前提下):
final class CustomAccelerationUnit: UnitAcceleration, ConvertibleUnit { static func allUnits(per coefficient: Decimal?) -> [UnitDescriptor<CustomAccelerationUnit>] { guard let mps2 = UnitAcceleration.metersPerSecondSquared as? CustomAccelerationUnit, let gravity = UnitAcceleration.gravity as? CustomAccelerationUnit else { fatalError("无法将原生加速度单位转换为CustomAccelerationUnit类型") } return [ UnitDescriptor(name: String(localized: "unit.acceleration.meters-per-second-squared.name"), unit: mps2), UnitDescriptor(name: String(localized: "unit.acceleration.gravity.name"), unit: gravity) ] } }
方式二:扩展父类提供Self类型访问器
通过扩展父类,为所有子类提供返回Self类型的静态属性,简化子类实现:
extension UnitAcceleration { static var metersPerSecondSquaredSelf: Self { metersPerSecondSquared as! Self } static var gravitySelf: Self { gravity as! Self } } final class CustomAccelerationUnit: UnitAcceleration, ConvertibleUnit { static func allUnits(per coefficient: Decimal?) -> [UnitDescriptor<CustomAccelerationUnit>] { return [ UnitDescriptor(name: String(localized: "unit.acceleration.meters-per-second-squared.name"), unit: .metersPerSecondSquaredSelf), UnitDescriptor(name: String(localized: "unit.acceleration.gravity.name"), unit: .gravitySelf) ] } }
核心思路总结
- 灵活的关联类型约束:通过
UnitType关联类型,既保证了返回单位的维度一致性,又避免了非final类的类型安全警告。 - 利用原生类型特性:Foundation提供的
Dimension子类静态属性本身就是对应类型,直接使用即可满足非final类的需求。 - 安全类型转换:对于自定义final子类,由于明确知道父类单位实例可以向上转换为子类(只要子类未重写属性类型),强制转换是安全的。
内容的提问来源于stack exchange,提问作者Michael Hulet

