如何在 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"
}
让我们假设如下:
- 每个 ENUM 都有 .unknown 大小写
- 我需要用通用的东西交换扩展中的枚举类型。
一定有一些技巧可以避免多次实现相同的扩展,只是改变类型。
更新
正如 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"
}
你的问题的本质是你想避免在实现 Argo 的 JSONDecodable
时为所有这些枚举编写样板代码。看起来您还添加了一个 create
方法,它不是 JSONDecodable
:
类型签名的一部分
public protocol JSONDecodable {
typealias DecodedType = Self
class func decode(JSONValue) -> DecodedType?
}
很遗憾,这无法完成。 Swift 协议不是混入。 除了运算符,它们不能包含任何代码。 (我真的希望这在 Swift 的未来更新中得到 "fixed"。协议的可覆盖默认实现会很棒。)
您当然可以通过几种方式简化您的实施:
- 正如 Tony DiPasquale 所建议的那样,去掉
.unknown
并使用可选项。 (此外,您应该将其命名为 .Unknown
。Swift 枚举值的约定是以大写字母开头。证明?看看 Apple 所做的每个枚举。我找不到一个例如,它们以小写字母开头。)
- 通过使用可选值,您的
create
现在只是 init?
的功能别名,可以非常简单地实现。
- 正如 Tony 建议的那样,创建一个全局通用函数来处理
decode
。他没有建议的是使用它来实现 JSONDecodable.decode
,尽管他可能认为这是暗示的。
- 作为元建议,使用 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 了解使这成为可能的实现。
我有大约 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"
}
让我们假设如下:
- 每个 ENUM 都有 .unknown 大小写
- 我需要用通用的东西交换扩展中的枚举类型。
一定有一些技巧可以避免多次实现相同的扩展,只是改变类型。
更新
正如 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"
}
你的问题的本质是你想避免在实现 Argo 的 JSONDecodable
时为所有这些枚举编写样板代码。看起来您还添加了一个 create
方法,它不是 JSONDecodable
:
public protocol JSONDecodable {
typealias DecodedType = Self
class func decode(JSONValue) -> DecodedType?
}
很遗憾,这无法完成。 Swift 协议不是混入。 除了运算符,它们不能包含任何代码。 (我真的希望这在 Swift 的未来更新中得到 "fixed"。协议的可覆盖默认实现会很棒。)
您当然可以通过几种方式简化您的实施:
- 正如 Tony DiPasquale 所建议的那样,去掉
.unknown
并使用可选项。 (此外,您应该将其命名为.Unknown
。Swift 枚举值的约定是以大写字母开头。证明?看看 Apple 所做的每个枚举。我找不到一个例如,它们以小写字母开头。) - 通过使用可选值,您的
create
现在只是init?
的功能别名,可以非常简单地实现。 - 正如 Tony 建议的那样,创建一个全局通用函数来处理
decode
。他没有建议的是使用它来实现JSONDecodable.decode
,尽管他可能认为这是暗示的。 - 作为元建议,使用 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 了解使这成为可能的实现。