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

时间:2015-05-07 19:27:43

标签: ios swift protocols

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

的开源单元框架的一部分

2 个答案:

答案 0 :(得分:2)

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

我建议使用类似于我在下面概述的设置。它引用了我以前的答案 - How to represent magnitude for mass in Swift?

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

我会让单位这样:

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
}

然后我会像这样制作群众/卷:

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

答案 1 :(得分:2)

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

与表示class ChildClass : ParentClass继承自ChildClass的{​​{1}}相反,ParentClass的含义略有不同,告诉枚举的rawType enum MassUnit : String,而不是枚举继承了String类型。

所以String不是String类型。您MassUnit的{​​{1}}类型为String,但要访问它,您需要调用枚举的MassUnit属性以获得rawValue等效项。

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