如何在方法参数中使用 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
的开源单元框架的一部分
在处理您的单位转换时,我建议您在按照上述设置进行转换时不要尝试使用 String
s 来表示单位。这将使您的代码变得复杂,因为每次您想要进行转换时,都需要检查 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
继承自ParentClass
的class ChildClass : ParentClass
相反,enum MassUnit : String
的含义略有不同,表示的rawType enum是String
,并不是enum继承了String类型
所以MassUnit
不是字符串类型。您 MassUnit
的 rawValue
是字符串类型,但要访问它,您需要调用枚举的 rawValue
属性 以获得 String
等价物.
因此,mutating func convert(#toUnit: String)
和 mutating func convert(#toUnit: MassType)
不兼容,因为 MassType
本身不是 String
。只有它的 rawValue
是。
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
的开源单元框架的一部分在处理您的单位转换时,我建议您在按照上述设置进行转换时不要尝试使用 String
s 来表示单位。这将使您的代码变得复杂,因为每次您想要进行转换时,都需要检查 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
继承自ParentClass
的class ChildClass : ParentClass
相反,enum MassUnit : String
的含义略有不同,表示的rawType enum是String
,并不是enum继承了String类型
所以MassUnit
不是字符串类型。您 MassUnit
的 rawValue
是字符串类型,但要访问它,您需要调用枚举的 rawValue
属性 以获得 String
等价物.
因此,mutating func convert(#toUnit: String)
和 mutating func convert(#toUnit: MassType)
不兼容,因为 MassType
本身不是 String
。只有它的 rawValue
是。