如何在Swift中简化几乎相同的枚举扩展

bla*_*acx 6 generics enums json ios swift

我有大约20个枚举的扩展名,如下所示:

extension CurrencyValue : JSONDecodable {
    static func create(rawValue: String) -> CurrencyValue {
        if let value = CurrencyValue(rawValue: rawValue) {
            return value
        }
        return .unknown
    }

    static func decode(j: JSONValue) -> CurrencyValue? {
        return CurrencyValue.create <^> j.value()
    }
}

extension StatusValue : JSONDecodable {
    static func create(rawValue: String) -> StatusValue {
        if let value = StatusValue(rawValue: rawValue) {
            return value
        }
        return .unknown
    }

    static func decode(j: JSONValue) -> StatusValue? {
        return StatusValue.create <^> j.value()
    }
}
Run Code Online (Sandbox Code Playgroud)

它们几乎是相同的,除了枚举类型名称,我有20个 - 这显然是非常愚蠢的.有没有人知道如何将它们减少到一个,也许是通过使用泛型?我现在不知道.

UPDATE

枚举就像这样简单:

enum CurrencyValue : String {
    case EUR = "EUR"
    case unknown = "unknown"
}

enum StatusValue : String {
    case ok = "ok"
    case pending = "pending"
    case error = "error"
    case unknown = "unknown"
}
Run Code Online (Sandbox Code Playgroud)

我们假设以下内容:

  1. 每个ENUM都有.unknown案例
  2. 我需要在扩展中用通用的东西交换枚举类型.

必须有一些技巧,不要多次实现相同的扩展,只需改变类型.

UPDATE

正如Gregory Higley在下面所说,我使用的是JSON lib Argo.你可以阅读那里的运营商.

Gre*_*ley 3

您问题的本质是您希望在实现 Argo 时避免为所有这些枚举编写样板代码JSONDecodable。看起来您还添加了一个create方法,该方法不是 的类型签名的一部分JSONDecodable

public protocol JSONDecodable {
  typealias DecodedType = Self
  class func decode(JSONValue) -> DecodedType?
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,这无法做到。Swift 协议不是 mixins。除运算符外,它们不能包含任何代码。(我真的希望这个问题在 Swift 的未来更新中得到“修复”。协议的可重写默认实现将是令人惊奇的。)

当然,您可以通过多种方式简化您的实现:

  1. 正如托尼·迪帕斯夸莱(Tony DiPasquale)建议的那样,摆脱.unknown并使用选项。(另外,你应该称之为.Unknown。Swift 枚举值的约定是以大写字母开头。证明?看看 Apple 所做的每个枚举。我找不到一个以小写字母开头的示例.)
  2. 通过使用选项, yourcreate现在只是 的功能别名init?,并且可以非常简单地实现。
  3. 正如托尼建议的那样,创建一个全局通用函数来处理decode. 尽管他可能认为这是暗示的,但他没有建议的是使用它来实现JSONDecodable.decode
  4. 作为元建议,请使用 Xcode 的代码片段功能创建一个片段来执行此操作。应该很快。

应提问者的要求,这里有一个来自游乐场的快速实现。我从来没有用过Argo。事实上,在我看到这个问题之前,我从来没有听说过它。我简单地通过应用我对 Swift 的了解来检查 Argo 的源代码并推理出来来回答这个问题。该代码是直接从游乐场复制的。它没有使用Argo,但使用了相关部分的合理复制品。归根结底,这个问题与Argo无关。这是关于 Swift 的类型系统的,下面代码中的所有内容都有效地回答了问题并证明它是可行的:

enum JSONValue {
    case JSONString(String)
}

protocol JSONDecodable {
    typealias DecodedType = Self
    class func decode(JSONValue) -> DecodedType?
}

protocol RawStringInitializable {
    init?(rawValue: String)
}

enum StatusValue: String, RawStringInitializable, JSONDecodable {
    case Ok = "ok"
    case Pending = "pending"
    case Error = "error"

    static func decode(j: JSONValue) -> StatusValue? {
        return decodeJSON(j)
    }
}

func decodeJSON<E: RawStringInitializable>(j: JSONValue) -> E? {
    // You can replace this with some fancy Argo operators,
    // but the effect is the same.
    switch j {
    case .JSONString(let string): return E(rawValue: string)
    default: return nil
    }
}

let j = JSONValue.JSONString("ok")
let statusValue = StatusValue.decode(j)
Run Code Online (Sandbox Code Playgroud)

这不是伪代码。它是直接从工作的 Xcode Playground 复制的。

如果您创建了协议RawStringInitializable并让所有枚举都实现了它,那么您将获得成功。由于您的枚举都具有关联的String原始值,因此它们无论如何都会隐式实现此接口。您只需做出声明即可。全局decodeJSON函数使用此协议以多态方式处理所有枚举。