您是否可以定义一个枚举来表示模型中属性的已知值,同时仍然允许从服务器返回未知值?
简短回答:是!
作为我们应用程序的一部分,我们定义了一组功能标志,该应用程序用于根据一组条件来启用/禁用某些功能。我们将这些值定义为一个简单的枚举,并将其标记为Codable
,编译器将自动为我们处理编码/解码。这是枚举。
enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
}
我们遇到的麻烦是,我们需要我们的应用程序来处理未来的案件,以及定义新标志的时期。通常,这意味着您将为每个使用此枚举的类编写自己的编码器/解码器方法,而忽略应用程序在编译时不知道的任何标志,但这些标志非常耗时且容易出错。
但是如果让枚举本身在运行时优雅地处理未知值怎么办?好吧,这就是我在下面介绍的解决方案。
喜欢这个建议的solution!一个小建议,如果系统遇到未知类型,请添加一些错误处理。
init?(rawValue: String) {
if let item = Self.allCases.first(where: { $0.rawValue == rawValue }) {
self = item
} else {
self = Self.other(rawValue)
if #available(iOS 12.0, *) {
os_log(.error, "Unknown FeatureFlag: %s", rawValue)
} else {
print("Error: Unknown FeatureFlag: \(rawValue)")
}
}
}
如上所述,我们的应用程序具有一组已知的功能标志。首先,可以这样定义它们。
enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
}
很简单。但是同样,现在定义为FeatureFlags
的任何属性只能处理那些特定的已知类型。如果在解码过程中遇到了不在该列表中的内容,除非您手动编写了解码逻辑,否则解码器将失败。
但是,如果没有必要怎么办?
技巧是定义另外一种情况,other
,其关联值类型为String
,像这样。这种情况下处理所有交给它的未知类型。
这是我们更新的枚举:
enum FeatureFlag : Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case other(String)
}
第一个问题是,由于other
具有关联的值,CaseIterable
无法再自动合成,因此我们必须自己手动实现。让我们在这里做...
extension FeatureFlag : CaseIterable {
typealias AllCases = [Self]
static let allCases:AllCases = [
.allowsTrading
.allowsFundScreener
.allowsFundsTransfer
]
}
[您会注意到我在这里特别忽略了other
,因为此版本的代码不知道other
中保存的值,因此我们可以将其视为根本不存在。
由于与未自动实现CaseIterable
相同的原因-other
具有关联的值-我们还必须手动实现RawRepresentable
,但这实际上是魔术自动处理未知值的地方。
诀窍是在实例化枚举时,根据原始值在allCases
中搜索已知类型,如果找到,则使用它。如果不是,请使用other
类型,将未知值放入其中。
同样,在返回途中,首先检查它是否为other
类型,如果是,则返回相关值。否则,返回描述已知情况的字符串。
这是两者的实现:
extension FeatureFlag : RawRepresentable {
init?(rawValue: String) {
self = Self.allCases.first{ $0.rawValue == rawValue } ?? Self.other(rawValue)
}
var rawValue: String {
switch self {
case let .other(value) : return value
default : return String(describing:self)
}
}
}
注意:这里我只是将案例本身用作原始值。如果您的枚举值需要与服务器上的其他值匹配,那么您当然可以手动扩展其他情况,例如,...
var rawValue: String {
switch self {
case .allowsTrading : return "ALLOWS_TRADING"
case .allowsFundScreener : return "ALLOWS_FUND_SCREENER"
case .allowsFundsTransfer : return "ALLOWS_FUNDS_TRANSFER"
case let .other(value) : return value
}
}
也基于原始值进行比较,因此,由于上述所有原因,所有这三个值均相等...
let a = FeatureFlag.allowsTrading
let b = FeatureFlag(rawValue: "allowsTrading")!
let c = FeatureFlag.other("allowsTrading")
let x = a == b // x is 'true'
let y = a == c // y is 'true'
let z = b == c // z is 'true'
另外,由于原始可表示值是可哈希的字符串,因此您也可以通过简单地指定其与该协议的一致性来使此枚举Hashable
(因此也可以是Equatable
)。>
extension FeatureFlag : Hashable {}
现在您可以将其用于集合中,也可以用作字典中的键。从上方使用“ a”,“ b”和“ c”-再次相等,您可以像这样使用它们...
字符串编码或解码为该枚举类型,但是仍然可以访问您关心的已知情况,而无需在模型类型中编写任何自定义解码逻辑。当您“了解”新类型时,只需添加新案例,您就可以开始使用了!var items = [FeatureFlag:Int]() items[a] = 42 print(items[a] ?? -1) // prints 42 print(items[b] ?? -1) // prints 42 print(items[c] ?? -1) // prints 42
使用上面的代码,您现在可以将any
享受!