[英]Can Swift enums have multiple raw values?
不,枚舉不能有多個原始值 - 它必須是單個值,實現Equatable
協議,並且可以如文檔中所述進行文字轉換。
我認為在您的情況下,最好的方法是使用錯誤代碼作為原始值,並使用由預先填充的靜態字典支持的屬性,錯誤代碼作為鍵,文本作為值。
你有幾個選擇。 但它們都不涉及原始值。 原始值並不是完成任務的正確工具。
我個人強烈建議不要在每個枚舉案例中使用多個關聯值。 關聯的值應該是顯而易見的(因為它們沒有參數/名稱),並且有一個以上的值會嚴重混淆水。
也就是說,這是語言讓你做的事情。 如果這是您需要的東西,這也允許您對每個案例進行不同的定義。 例子:
enum ErrorType {
case teapot(String, Int)
case skillet(UInt, [CGFloat])
}
元組是 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
剩下的就是在MyError
和ErrorInfo
實例之間進行映射:
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:
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 標簽的字符串值。
因此不再需要定義和維護一個方便的函數來打開每個 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.