簡體   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