You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非final Dimension子类遵循ConvertibleUnit协议的Swift实现问题

解决Dimension子类遵循单位枚举协议的类型兼容问题

我正在构建一款计量单位转换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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 08:09:59