如何在方法参数中使用 Raw 类型来遵守协议方法?

How to adhere to protocol method with a Raw type in method argument?

protocol Measurement {
     mutating func convert(#toUnit: String)
}

enum MassUnit : String {
    case Milligram = "mg"
}

enum VolumeUnit : String {
     case Milliliter = "ml"
}

struct Mass : Measurement {
     mutating func convert(#toUnit: MassUnit)
     // Build error: Does not adhere to 'Measurement'
}

struct Volume : Measurement {
     mutating func convert(#toUnit: VolumeUnit)
     // Build error: Does not adhere to 'Measurement'
}

func +<T: Measurement> (left:T, right:T) -> Measurement {
    let newRightValue  = right.convert(toUnit: left.unit)
    return T(quantity: left.quantity + newRightValue.quantity , unit: left.unit)
}

如何让 Mass 正确遵守 Measurement?需要对 Measurement 协议进行哪些更改才能使其与 String 类型的枚举一起使用?

问题更新了更多关于为什么转换方法签名应该说明给定参数的信息。该代码是我正在构建的名为 Indus Valley

的开源单元框架的一部分

在处理您的单位转换时,我建议您在按照上述设置进行转换时不要尝试使用 Strings 来表示单位。这将使您的代码变得复杂,因为每次您想要进行转换时,都需要检查 String 是否可以转换为其各自的枚举。另外,如果您想使用 MassUnit/VolumeUnit 而不是 String 怎么办?

我建议使用类似于我在下面概述的设置。它引用了我之前的回答 -

(注意 - 我排除了与音量有关的任何内容,因为它与质量的实现基本相同)

我会这样制作单位:

protocol UnitProtocol {
    var magnitude: Int { get }

    init?(rawValue: String)
}

// Taken from my previous answer.
enum MassUnit: String, UnitProtocol, Printable {
    case Milligram = "mg"
    case Gram      = "g"

    var magnitude: Int {
        let mag: Int

        switch self {
            case .Milligram: mag = -3
            case .Gram     : mag =  0
        }

        return mag
    }

    var description: String {
        return rawValue
    }
}

// Not making this a method requirement of `UnitProtocol` means you've only got to 
// write the code once, here, instead of in every enum that conforms to `UnitProtocol`.
func ordersOfMagnitudeFrom<T: UnitProtocol>(unit1: T, to unit2: T) -> Int {
    return unit1.magnitude - unit2.magnitude
}

然后我会像这样 masses/volumes:

protocol UnitConstruct {
    typealias UnitType: UnitProtocol
    var amount: Double   { get }
    var unit  : UnitType { get }

    init(amount: Double, unit: UnitType)
}

struct Mass : UnitConstruct {
    let amount: Double
    let unit  : MassUnit
}

现在是转换功能!使用全局函数意味着您无需为符合 UnitConstruct.

的每种类型重写代码
func convert<T: UnitConstruct>(lhs: T, toUnits unit: T.UnitType) -> T {
    let x = Double(ordersOfMagnitudeFrom(lhs.unit, to: unit))
    return T(amount: lhs.amount * pow(10, x), unit: unit)
}

// This function is for converting to different units using a `String`,
// as asked in the OP.
func convert<T: UnitConstruct>(lhs: T, toUnits unit: String) -> T? {
    if let unit = T.UnitType(rawValue: unit) {
        return convert(lhs, toUnits: unit)
    }

    return nil
}

然后您可以像这样使用之前的代码:

let mass1 = Mass(amount: 1.0, unit: .Gram)
let mass2 = convert(mass1, toUnits: .Milligram) // 1000.0 mg

// Or, converting using Strings:
let right = convert(mass1, toUnits: "mg")       // Optional(1000.0 mg)
let wrong = convert(mass1, toUnits: "NotAUnit") // nil

您可能会将 enum MassUnit : String 与继承混淆。

与表示ChildClass继承自ParentClassclass ChildClass : ParentClass相反,enum MassUnit : String的含义略有不同,表示的rawType enumString,并不是enum继承了String类型

所以MassUnit不是字符串类型。您 MassUnitrawValue 是字符串类型,但要访问它,您需要调用枚举的 rawValue 属性 以获得 String 等价物.

因此,mutating func convert(#toUnit: String)mutating func convert(#toUnit: MassType) 不兼容,因为 MassType 本身不是 String。只有它的 rawValue 是。