繁体   English   中英

Swift 枚举可以有多个原始值吗?

[英]Can Swift enums have multiple raw values?

我想将两个原始值关联到一个枚举实例(想象一个代表错误类型的枚举,我希望Error.Teapot具有一个值为 418 的 Int 类型属性code ,以及一个设置为I'm a teapot的 String 属性。)

请注意此处原始值关联值之间的区别——我希望所有Teapot实例的code都是 418,我不希望每个Teapot实例都有一个唯一的关联值。

有没有比将计算属性添加到switch ed on self以查找适当值的枚举更好的方法?

不,枚举不能有多个原始值 - 它必须是单个值,实现Equatable协议,并且可以如文档中所述进行文字转换。

我认为在您的情况下,最好的方法是使用错误代码作为原始值,并使用由预先填充的静态字典支持的属性,错误代码作为键,文本作为值。

你有几个选择。 但它们都不涉及原始值。 原始值并不是完成任务的正确工具。

选项 1(一般):相关值

我个人强烈建议不要在每个枚举案例中使用多个关联值。 关联的值应该是显而易见的(因为它们没有参数/名称),并且有一个以上的值会严重混淆水。

也就是说,这是语言让你做的事情。 如果这是您需要的东西,这也允许您对每个案例进行不同的定义。 例子:

enum ErrorType {
    case teapot(String, Int)
    case skillet(UInt, [CGFloat])
}

选项 2(更好):元组! 和计算属性!

元组是 Swift 的一个很棒的特性,因为它们赋予你创建临时类型的能力。 这意味着您可以在线定义它。 甜的!

如果您的每个错误类型都将有一个代码和一个描述,那么您可以拥有一个计算info属性(希望有一个更好的名称?)。 见下文:

enum ErrorType {
    case teapot
    case skillet

    var info: (code: Int, description: String) {
        switch self {
        case .teapot:
            return (418, "Hear me shout!")
        case .skillet:
            return (326, "I'm big and heavy.")
        }
    }
}

调用它会容易得多,因为您可以使用美味、美味的点语法

let errorCode = myErrorType.info.code

我创建了一种模拟这个的方法(与 Marcos Crispino 在他的回答中建议的没有什么不同)。 远非完美的解决方案,但允许我们为我们想要获得的每个不同属性避免那些讨厌的开关案例。

诀窍是使用结构作为“属性/数据”持有者,并将其用作枚举本身中的 RawValue。

它有一些重复,但到目前为止它对我来说很好。 每次要添加新的枚举 case 时,编译器都会提醒您在 rawValue getter 中填写多余的 case,这应该提醒您更新init? 这会提醒您在结构上创建新的静态属性。

要旨

代码要点:

enum VehicleType : RawRepresentable {

    struct Vehicle : Equatable {
        let name: String
        let wheels: Int

        static func ==(l: Vehicle, r: Vehicle) -> Bool {
            return l.name == r.name && l.wheels == r.wheels
        }

        static var bike: Vehicle {
            return Vehicle(name: "Bicycle", wheels: 2)
        }

        static var car: Vehicle {
            return Vehicle(name: "Automobile", wheels: 4)
        }

        static var bus: Vehicle {
            return Vehicle(name: "Autobus", wheels: 8)
        }
    }

    typealias RawValue = Vehicle

    case car
    case bus
    case bike

    var rawValue: RawValue {
        switch self {
        case .car:
            return Vehicle.car
        case .bike:
            return Vehicle.bike
        case .bus:
            return Vehicle.bus
        }
    }

    init?(rawValue: RawValue) {
        switch rawValue {
        case Vehicle.bike:
            self = .bike
        case Vehicle.car:
            self = .car
        case Vehicle.bus:
            self = .bus
        default: return nil
        }
    }
}

VehicleType.bike.rawValue.name
VehicleType.bike.rawValue.wheels
VehicleType.car.rawValue.wheels

VehicleType(rawValue: .bike)?.rawValue.name => "Bicycle"
VehicleType(rawValue: .bike)?.rawValue.wheels => 2
VehicleType(rawValue: .car)?.rawValue.name => "Automobile"
VehicleType(rawValue: .car)?.rawValue.wheels => 4
VehicleType(rawValue: .bus)?.rawValue.name => "Autobus"
VehicleType(rawValue: .bus)?.rawValue.wheels => 8

不,您不能有多个与枚举关联的原始值。

在您的情况下,您可以使原始值等于代码,并具有与描述相关的值。 但我认为计算属性方法是这里的最佳选择。

如果您想为 YourError 拥有许多静态属性,一种解决方法可能是导入属性列表; 您可以将根对象设置为字典,将您的枚举原始值作为每个对象的键,让您轻松检索对象的静态结构化数据。

这有一个导入和使用 plist 的示例: http : //www.spritekitlessons.com/parsing-a-property-list-using-swift/

对于简单的错误描述来说,这可能是矫枉过正,为此您可以只使用硬编码的静态函数和您的枚举值的 switch 语句,返回您需要的错误字符串。 只需将静态函数放在与枚举相同的 .swift 文件中。

例如,

static func codeForError(error : YourErrorType) -> Int {
    switch(error) {
        case .Teapot:
            return "I'm a Teapot"
        case .Teacup:
            return "I'm a Teacup"
        ...
        default:
            return "Unknown Teaware Error"
    }
}

这具有更好地适应本地化的好处(与 .plist 解决方案相比)。 但是,为此目的,.plist 可以只包含用于检索正确本地化的键,而不是错误字符串。

首先,假设您要存储代码和消息,您可以对RawValue使用一个结构体

struct ErrorInfo {
    let code: Int
    let message: String
}

下一步是将枚举定义为RawRepresentable ,并使用ErrorInfo作为原始值:

enum MyError: RawRepresentable {
    typealias RawValue = ErrorInfo

    case teapot

剩下的就是在MyErrorErrorInfo实例之间进行映射:

static private let mappings: [(ErrorInfo, MyError)] = [
        (ErrorInfo(code: 418, message: "I'm a teapot"), .teapot)
    ]

有了上面的内容,让我们构建枚举的完整定义:

enum MyError: RawRepresentable {
    static private let mappings: [(ErrorInfo, MyError)] = [
    (ErrorInfo(code: 418, message: "I'm a teapot"), .teapot)
    ]

    case teapot

    init?(rawValue: ErrorInfo) {
        guard let match = MyError.mappings.first(where: { $0.0.code == rawValue.code && $0.0.message == rawValue.message}) else {
            return nil
        }
        self = match.1
    }

    var rawValue: ErrorInfo {
        return MyError.mappings.first(where: { $0.1 == self })!.0
    }
}

一些注意事项:

  • 您只能使用错误代码进行匹配,但是如果消息不同,这可能会导致原始值不一致
  • 具有某种自定义类型的原始值所需的样板代码量可能不会产生使用关联值的好处。

可能的解决方法可能是将自定义函数与枚举相关联

 enum ToolbarType : String{
        case Case = "Case", View="View", Information="Information"
        static let allValues = [Case, View, Information]

        func ordinal() -> Int{
            return ToolbarType.allValues.index(of: self)!
        }
 }

可以用作

 for item in ToolbarType.allValues {
        print("\(item.rawValue): \(item.ordinal())")
 }

输出

Case: 0
View: 1
Information: 2

可能你可以有额外的功能来将枚举类型关联到不同的值

这并没有特别回答您的问题,该问题要求找到一种比通过self进行switch以查找适当值更好的方法,但该答案可能对将来需要一种简单方法来获取字符串的人仍然有用来自定义为整数类型的枚举。

enum Error: UInt {
    case Teapot = 418
    case Kettle = 419

    static func errorMessage(code: UInt) -> String {
        guard let error = Error(rawValue: code) else {
            return "Unknown Error Code"
        }

        switch error {
        case .Teapot:
            return "I'm a teapot!"
        case .Kettle:
            return "I'm a kettle!"
        }
    }
}

这样,我们可以通过两种方式获取errorMessage:

  1. 使用整数(例如,作为错误代码从服务器返回)
  2. 使用枚举值(我们为枚举定义的rawValue

选项1:

let option1 = Error.errorMessage(code: 418)
print(option1)  //prints "I'm a teapot!"

选项 2:

let option2 = Error.errorMessage(code: Error.Teapot.rawValue)
print(option2)  //prints "I'm a teapot!"    

在现代版本的 Swift 中,即使没有使用: String rawValue 声明该枚举,也可以获得枚举 case 标签的字符串值。

如何在 Swift 中获取枚举值的名称?

因此不再需要定义和维护一个方便的函数来打开每个 case 以返回一个字符串文字。 此外,即使未指定原始值类型,这也会自动适用于任何枚举。

这至少允许您通过同时拥有一个 real : Int rawValue 以及用作 case 标签的字符串来拥有“多个原始值”。

我认为这很棘手,我创建了自己的想法,如下所示:

enum Gender:NSNumber
{
    case male = 1
    case female = 0

    init?(strValue: String?) {
        switch strValue {
        case Message.male.value:
            self = .male
        case Message.female.value:
            self = .female
        default: return nil
        }
    }

    var strValue: String {
        switch self {
        case .male:
            return Message.male.value
        case .female:
            return Message.female.value
        }
    }
}

首先,枚举应该只有一个原始值。 但是,如果您想拥有可以使用多个原始值的东西……有一种方法可以“破解”它,但是您必须自己使其可编码和可散列,实现自定义初始化等。

enum MyCustomEnum: Codable, Hashable {

// duplicate every case with associated value of Codable.Type
case myFirstCase, _myFirstCase(Codable.Type)
case mySecondCase, _mySecondCase(Codable.Type)
case myThirdCase, _myThirdCase(Codable.Type)
case unknown(Any), _unknown(Codable.Type, Any) // handles unknown values

// define an allCases value to determine the only values your app 'sees'.
static var allCases: [Self] {
    return [
        .myFirstCase,
        .mySecondCase,
        .myThirdCase
        // unknown(String) // you can add unknown as well, but this is too mask any unknown values.
    ]
}

static func == (lhs: MyCustomEnum, rhs: MyCustomEnum) -> Bool {
    return lhs.stringValue == rhs.stringValue // can be either one of your custom raw values.
}

// add this per raw value. In this case one for Int and one for String
init(rawValue: Int) {
    guard let value = Self.allCases.first(where:{ $0.intValue == rawValue }) else {
        self = ._unknown(Int.self, rawValue)
        return
    }
    switch value {
    case .myFirstCase: self = ._myFirstCase(Int.self)
    case .mySecondCase: self = ._mySecondCase(Int.self)
    case .myThirdCase: self = ._myThirdCase(Int.self)
    default: self = ._unknown(Int.self, rawValue)
    }
}

init(rawValue: String) {
    guard let value = Self.allCases.first(where:{ $0.stringValue == rawValue }) else {
        self = ._unknown(String.self, rawValue)
        return
    }
    switch value {
    case .myFirstCase: self = ._myFirstCase(String.self)
    case .mySecondCase: self = ._mySecondCase(String.self)
    case .myThirdCase: self = ._myThirdCase(String.self)
    default: self = ._unknown(Int.self, rawValue)
    }
}

// add this per raw value. In this case one for Int and one for String
var intValue: Int {
    switch self {
    case .myFirstCase, ._myFirstCase(_): return 1
    case .mySecondCase, ._mySecondCase(_): return 2
    case .myThirdCase, ._myThirdCase(_): return 3
    case .unknown(let value), ._unknown(_, let value): return value as? Int ?? -1 // you can also choose to let intValue return optional Int.
    }
}

var stringValue: String {
    switch self {
    case .myFirstCase, ._myFirstCase(_): return "my first case"
    case .mySecondCase, ._mySecondCase(_): return "my second case"
    case .myThirdCase, ._myThirdCase(_): return "my third case"
    case .unknown(let value), ._unknown(_, let value): return value as? String ?? "not a String" // you can also choose to let stringValue return optional String.
    }
}

// determine the codable type using Mirror
private func getCodableType() -> Codable.Type? {
    let mirrorOfModuleType = Mirror.init(reflecting: self)
    guard let childOfModuleType = mirrorOfModuleType.children.first else { // no children, means no associated values.
        return nil
    }
    let value = childOfModuleType.value // can be either Codable.Type, String or (Codable.Type & String)
    if let rawValue = value as? Codable.Type {
        return rawValue
    } else {
        guard let rawValue = value as? (Codable.Type, String) else {
            // unknown(String), we don't know the rawValue as given, but try in this part of the code to guess what type fits best.
            if self.stringValue != "\(self.intValue)" { // e.g. "1" might match 1 but "1.0" and 1 don't match
                return String.self
            } else {
                return Int.self // return either a default value, or nil. It's your choice.
            }
        }
        return rawValue.0
    }
}

// confine to hashable using getCodableType
func hash(into hasher: inout Hasher) {
    if self.getCodableType() is String.Type {
        hasher.combine(self.stringValue)
    } else { // if you don't call hasher.combine at all, you can expect strange issues. If you do not know the type, choose one that is most common.
        hasher.combine(self.intValue)
    }
}

// confine to Decodable
init(from decoder: Decoder) throws {
    if let rawValue = try? Int.init(from: decoder) {
        self.init(rawValue: rawValue)
    } else if let rawValue = try? String.init(from: decoder) {
        self.init(rawValue: rawValue)
    } else {
        throw DecodingError.valueNotFound(Self.self, DecodingError.Context(codingPath: [], debugDescription: "no matching value was found"))
    }
}

// confine to Encodable using getCodableType
func encode(to encoder: Encoder) throws {
    let rawValue = self.getCodableType()
    if rawValue is String.Type {
        try self.stringValue.encode(to: encoder)
    } else if rawValue is Int.Type {
        try self.intValue.encode(to: encoder)
    } else {
        // getCodableType returns nil if it does not know what value it is. (e.g. myFirstCase without associated value) If you want to support this as well, you can encode using one of your rawValues to the encoder.
        throw EncodingError.invalidValue(Self.self, EncodingError.Context.init(codingPath: [], debugDescription: "this enum does not have a correct value", underlyingError: nil))
    }
}

}

此代码可扩展到任意数量的原始值,只要它们是可编码的

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM