在 Swift 中枚举具有相同值的多个案例

Enum multiple cases with the same value in Swift

在 C 中你可以让你的枚举有这个:

typedef enum _Bar {
    A = 0,
    B = 0,
    C = 1
} Bar;

在 Swift 中,我想制作等价物。然而,编译器抱怨它不是唯一的。我如何告诉它我希望两个案例具有相同的值?

enum Bar : Int {
    case A = 0
    case B = 0 // Does not work
    case C = 1
}

我试过了

case A | B = 0

case A, B = 0

但它似乎并没有像我想要的那样工作。

Swift 不允许 enum 的元素共享值。来自 "Raw Values" 标题下的枚举文档(强调我的):

Raw values can be strings, characters, or any of the integer or floating-point number types. Each raw value must be unique within its enumeration declaration.

我不确定你能做到。以下摘自苹果

“Unlike C and Objective-C, Swift enumeration members are not assigned a default integer value when they are created. In the CompassPoints example above, North, South, East and West do not implicitly equal 0, 1, 2 and 3. Instead, the different enumeration members are fully-fledged values in their own right, with an explicitly-defined type of CompassPoint.”

摘自:Apple Inc.“Swift 编程语言。”电子书。 https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11

由于枚举成员不隐式等于 0、1 等,编译器将每个成员视为唯一值。当您尝试复制它时,编译器会抱怨,因为它已经被创建了。

Swift 不支持重复值(或语义上的“别名”)。如果你不介意,你可以使用类似这样的东西来模仿它:

enum Foo: Int {
    case Bar = 0

    static var Baz:Foo {
        get {
            return    Bar
        }
    }
    static var Jar:Foo {
        get {
            return    Foo(rawValue: 0)!
        }
    }
}

对于 Swift 的最新版本,可以这样缩短:

enum Foo: Int {
    case bar = 0

    static var baz:Foo { .bar }
    static var jar:Foo { Foo(rawValue: 0)! }
}

请注意,Swift 已将其枚举变体的命名约定从 PascalCase to camelCase 更改为。

这是另一种解决方法:

enum Animal {

  case dog
  case cat
  case mouse
  case zebra

  var description: String {
    switch self {
    case .dog:
      return "dog"

    case .cat:
      return "dog"

    case .mouse:
      return "dog"

    case .zebra:
      return "zebra"

    default:
      break
    }
  }
}

我遇到了关于具有相同值的多个大小写的相同问题。谷歌搜索这个问题后,我更喜欢以下解决方案 computed properties of enum, which come from this post.


我发现这个纯粹是巧合,并且很惊讶在任何答案中都没有 Swift-y 黑魔法(当然,这仍然很好)。我个人不太喜欢 static 属性,因为它们会在其他地方引入开销,例如在模式匹配中。

我认为 "cleanest" 解决方案是依靠 RawRepresentable 并自行定义。然后你可以反驳 Swift 不支持开箱即用的主要原因:init?(rawValue:) 方法。通过仅将 String 指定为采用的原始值(即 enum MyEnum: String { ... }),编译器无法自行生成逻辑。它需要知道要跳过哪种情况,因为没有定义 "enough" 潜在的原始值(因为一个或多个被加倍)。自己采用该协议允许您简单地选择一种情况作为给定原始值字符串的 "default",并基本上防止使用原始值构造另一种情况(当然,常规构造仍然有效)。

不幸的是,这也是一些开销,因为前者 case myCase = "its_raw_value" 然后被分发到

  • 常规情况(没有原始值字符串):case myCase(定义)
  • init?(rawValue:)方法中的一个开关:... case "its_raw_value": return .myCase ...
  • 需要rawValue 属性中的开关:... case .myCase: return "its_raw_value" ...

我想我有一个很好的方法可以减少一点并防止原始值浮动太多。我写了一个游乐场来说明,并将其简单地粘贴在这里:


// define protocol and a default implementation
protocol SloppyRawRepresentable
where Self: RawRepresentable, Self.RawValue: Hashable, Self: CaseIterable {
}
extension SloppyRawRepresentable {
    init?(rawValue: RawValue) {
        var tempMapping = [RawValue: Self]()
        for oneCase in Self.allCases {
            // first come first served. any case after the first having the same
            // raw value is simply ignored, so the first is the "default"
            if tempMapping[oneCase.rawValue] == nil {
                tempMapping[oneCase.rawValue] = oneCase
            }
        }
        guard let candidate = tempMapping[rawValue] else { return nil }
        self = candidate
    }
}

// use it. Note we don't need the init
enum EventNames: SloppyRawRepresentable {
    typealias RawValue = String

    var rawValue: String {
        switch self {
        case .standardEvent: return "standard"
        case .joesConfig: return "iAmJoe"
        case .myConfig: return "iAmJoe"
        }
    }

    case standardEvent
    case joesConfig
    case myConfig
}

// some example output

print(EventNames.standardEvent)
print(EventNames.joesConfig)
print(EventNames.myConfig)

print(EventNames.standardEvent.rawValue)
print(EventNames.joesConfig.rawValue)
print(EventNames.myConfig.rawValue)

print(EventNames(rawValue: "standard")!)
print(EventNames(rawValue: "iAmJoe")!)
print(EventNames(rawValue: "iAmJoe")!)

print(EventNames(rawValue: "standard")!.rawValue)
print(EventNames(rawValue: "iAmJoe")!.rawValue)
print(EventNames(rawValue: "iAmJoe")!.rawValue)

它应该是不言自明的。案例定义和为其分配原始值仍然是分裂的,我认为没有办法解决这个问题,但它可以派上用场。当然,所有这些都需要 RawValue be 约束,我依靠 enum be CaseIterable 来轻松构建辅助字典,但我认为这应该没问题,性能方面。

免责声明:我不推荐这个。大多数人不会考虑最佳实践。有缺点,具体取决于应用程序。姑且说这些想法是理论的、假设的、不纯粹的,仅供思考

一种可能性是添加某种无意义的微分器,足以阻止 Swift 对不影响您在代码中使用原始值所做的操作的身份检查。

他是一个 Float 的例子,在音乐中,A♯(升 A)与 B♭(降 B)的频率相同。换句话说,它们都是同一事物的不同注释。

以微赫兹为单位测量的差异对音频引擎没有意义,但足以区分 Swift 枚举中的大小写值。换句话说,实际上,116.540和116.540001在大多数音乐应用中没有区别:

enum NoteFrequency : Float = {
   case A = 110.0, A_SHARP = 116.540, B_FLAT = 116.540001
}

从准容忍到丑陋:对于只分配和打印的字符串,您可以添加一个不可打印的字符来区分。

显然这有缺点(可能很严重),如果你做了这样的事情,后来你(或其他人)可能不知道你做了什么,并且编写了不考虑它的代码(例如使用在 String 比较中的枚举的 rawValue 中)并且它们的代码失败。要在字符串比较中进行这项工作,您可能必须做一些事情来忽略最后一个字符,或者在比较之前使用 myEnumString.rawValue.dropLast() 将其删除为不可打印的字符...

 enum Animals : String = {
     case CAT = "Feline\u{01}", LION = "Feline\u{02}"
 }

您每次都可以轻松地将 == 运算符覆盖为 return true,除非那是作弊。让我们以真正的方式做到这一点!


我不同意接受的答案:Swift 允许原始值相等,但 不允许 你写出解释性相同的文字。这可能会导致人们认为您所要求的是不可能的。但是,你的问题是可以回答的,我们将不得不稍微解决一下 Swift 的词汇语法。

i.e. 1.0 and 1.000 are interpretively identical Double literals, since the extra zeroes collapse.

最简单的证明是利用浮点类型underflow/overflow。文字本身并不相同,但值本身是相同的。

enum ExhibitA: Double {
    case a = 0.0
    case b = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
}
print(ExhibitA.a == ExhibitA.b) // true
print(ExhibitA.a.rawValue) // 0.0
print(ExhibitA.b.rawValue) // 0.0

所以我从技术上回答了你的问题,但这没有任何实用价值。让我们尝试让它适用于所有数字,并包括更多类型。


如果您让您的枚举案例成为 JSON 可解码的字符串原始值,并应用 ExpressibleByStringLiteral 协议将它们转换为指定的类型,您就可以做到这一点。

import Foundation

struct EnumWrapper<T: Codable & Equatable>: ExpressibleByStringLiteral, Equatable {
    var value: T
    init(_ t: T) { value = t }
    init(stringLiteral value: StringLiteralType) {
        try! self.init(JSONDecoder().decode(T.self, from: Data(value.utf8)))
    }
}


enum Foo: EnumWrapper<Int> {
    case a = "1"
    case b = "1 "
}
print(Foo.a == Foo.b) // true
print(Foo.a.rawValue.value) // 1
print(Foo.b.rawValue.value) // 1

太棒了!现在您可以将任何符合 Decodable.
的值用作原始值 即 4D 布尔数组:

enum Example: EnumWrapper<[[[[Bool]]]]> {
    case woah = "[[[[true, false, true], [false], []], []]]"
}
print(Example.woah.rawValue.value) // [[[[true, false, true], [false], []], []]]

此模式应用于集合或字典时很危险。在您 运行 以下代码的一半时间里,您会收到 运行 次错误,指出字典有重复键。

let foo : [Foo : Bool] = [
    .a : true,
    .b : false
]