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

时间:2015-01-08 21:15:50

标签: ios json generics swift enums

我有大约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. 我需要在扩展中用通用的东西交换枚举类型。
  3. 必须有一些技巧不能多次实现相同的扩展,只需更改类型。

    更新

    如下面的Gregory Higley所述,我使用JSON lib Argo。你可以阅读那里的运营商。

3 个答案:

答案 0 :(得分:3)

您的问题的实质是您希望在实施ArgoJSONDecodable时避免为所有这些枚举编写样板代码。您似乎还添加了create方法,该方法不属于JSONDecodable的类型签名:

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

不幸的是,这无法做到。 Swift协议不是mixins。除运算符外,它们不能包含任何代码。 (我真的希望在未来的Swift更新中得到“修复”。协议的可覆盖默认实现会很棒。)

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

  1. 正如Tony DiPasquale建议的那样,摆脱.unknown并使用选项。 (另外,你应该调用这个.Unknown。Swift枚举值的约定是用大写字母开始它们。证明?看看Apple所做的每一个枚举。我找不到他们开始的一个例子用小写字母。)
  2. 通过使用选项,create现在只是init?的功能别名,可以非常简单地实现。
  3. 正如Tony建议的那样,创建一个全局通用函数来处理decode。虽然他可能认为这是暗示的,但他没有暗示的是用这个来实现JSONDecodable.decode
  4. 作为元建议,使用Xcode的代码片段功能创建一个片段来执行此操作。应该很快。
  5. 根据提问者的要求,这是从操场上快速实施的。我从未使用过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全局函数使用此协议以多态方式处理所有枚举。

答案 1 :(得分:1)

如果您也可以提供枚举的示例,则可能更容易看到可以改进的内容。

只是看一下我会说你可能会考虑从所有枚举中删除.unknown,只是让可选类型代表.unknown.None。如果你这样做,你可以写:

extension StatusValue: JSONDecodable {
    static func decode(j: JSONValue) -> StatusValue? {
        return j.value() >>- { StatusValue(rawValue: $0) }
    }
}

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

修改

您可以使用泛型。如果您创建协议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: $0) }
}

最后,在Argo中你可以让你的生物解码功能如下:

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

答案 2 :(得分:1)

对于其他人来说,最新的答案是: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了解实现这一目标的实现。