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

How to simplify almost equal enum extensions in 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()
    }
}

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

更新

枚举就这么简单:

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

enum StatusValue : String {
    case ok = "ok"
    case pending = "pending"
    case error = "error"
    case unknown = "unknown"
}

让我们假设如下:

  1. 每个 ENUM 都有 .unknown 大小写
  2. 我需要用通用的东西交换扩展中的枚举类型。

一定有一些技巧可以避免多次实现相同的扩展,只是改变类型。

更新

正如 Gregory Higley below I use the JSON lib Argo 所述。您可以在那里阅读有关运算符的信息。

如果您也可以提供一个枚举示例,可能会更容易看出可以改进的地方。

看看这个我会说你可能想考虑从所有枚举中删除 .unknown 并且只让可选类型用 .None 表示 .unknown 值。如果你这样做,你可以写:

extension StatusValue: JSONDecodable {
    static func decode(j: JSONValue) -> StatusValue? {
        return j.value() >>- { StatusValue(rawValue: [=10=]) }
    }
}

现在不需要所有的创建功能。这将减少很多重复。

编辑:

您可以使用泛型。如果你像这样创建一个协议DecodableStringEnum

protocol DecodableStringEnum {
  init?(rawValue: String)
}

然后让你所有的枚举都符合它。您不必再编写任何代码,因为 init 带有原始值枚举。

enum CreatureType: String, DecodableStringEnum {
  case Fish = "fish"
  case Cat = "cat"
}

现在编写一个全局函数来处理所有这些情况:

func decodeStringEnum<A: DecodableStringEnum>(key: String, j: JSONValue) -> A? {
  return j[key]?.value() >>- { A(rawValue: [=13=]) }
}

最后,在 Argo 中你可以让你的生物解码函数看起来像这样:

static func decode(j: JSONValue) -> Creature? {
  return Creature.create
    <^> j <| "name"
    <*> decodeStringEnum("type", j)
    <*> j <| "description"
}

你的问题的本质是你想避免在实现 ArgoJSONDecodable 时为所有这些枚举编写样板代码。看起来您还添加了一个 create 方法,它不是 JSONDecodable:

类型签名的一部分
public protocol JSONDecodable {
  typealias DecodedType = Self
  class func decode(JSONValue) -> DecodedType?
}

很遗憾,这无法完成。 Swift 协议不是混入。 除了运算符,它们不能包含任何代码。 (我真的希望这在 Swift 的未来更新中得到 "fixed"。协议的可覆盖默认实现会很棒。)

您当然可以通过几种方式简化您的实施:

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

应提问者的要求,这是在 playground 中的快速实现。我从未使用过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)

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

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

对于其他任何人,最新的答案如下:https://github.com/thoughtbot/Argo/blob/td-decode-enums/Documentation/Decode-Enums.md

本质上,对于枚举

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

enum StatusValue : String {
    case ok = "ok"
    case pending = "pending"
    case error = "error"
    case unknown = "unknown"
}

您只需要做

extension CurrencyValue: Decodable {}
extension StatusValue: Decodable {}

此外,似乎已经反复指出,如果您只是摆脱 .Unknown 字段,而是使用可选值,则可以将对枚举的内置支持用作 RawRepresentable 类型。

另请参阅:https://github.com/thoughtbot/Argo/blob/master/Argo/Extensions/RawRepresentable.swift 了解使这成为可能的实现。