您是否可以定义枚举来表示诸如FeatureFlags之类的属性的已知值,同时仍然允许它处理未知值?

问题描述 投票:0回答:2

您是否可以定义一个枚举来表示模型中属性的已知值,同时仍然允许从服务器返回未知值?

简短回答:是!

作为我们应用程序的一部分,我们定义了一组功能标志,该应用程序用于根据一组条件来启用/禁用某些功能。我们将这些值定义为一个简单的枚举,并将其标记为Codable,编译器将自动为我们处理编码/解码。这是枚举。

enum FeatureFlag : String, CaseIterable, Codable {
    case allowsTrading
    case allowsFundScreener
    case allowsFundsTransfer
}

我们遇到的麻烦是,我们需要我们的应用程序来处理未来的案件,以及定义新标志的时期。通常,这意味着您将为每个使用此枚举的类编写自己的编码器/解码器方法,而忽略应用程序在编译时不知道的任何标志,但这些标志非常耗时且容易出错。

但是如果让枚举本身在运行时优雅地处理未知值怎么办?好吧,这就是我在下面介绍的解决方案。

swift enums codable
2个回答
1
投票

喜欢这个建议的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)")
        }
    }
}

1
投票

如上所述,我们的应用程序具有一组已知的功能标志。首先,可以这样定义它们。

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

字符串编码或解码为该枚举类型,但是仍然可以访问您关心的已知情况,而无需在模型类型中编写任何自定义解码逻辑。当您“了解”新类型时,只需添加新案例,您就可以开始使用了!

享受!

© www.soinside.com 2019 - 2024. All rights reserved.