问题
In C you could make your enums have this:
typedef enum _Bar {
A = 0,
B = 0,
C = 1
} Bar;
In Swift I want to make the equivalent. However, the compiler complains that it isn't unique. How do I tell it that I want two cases to have the same value?
enum Bar : Int {
case A = 0
case B = 0 // Does not work
case C = 1
}
I've tried
case A | B = 0
and
case A, B = 0
But it doesn't seem to work as I want it to.
回答1:
Swift doesn't support duplicated values (or "aliases" semantically). If you don't mind, you can mimic it by using something like this:
enum Foo: Int {
case Bar = 0
static var Baz:Foo {
get {
return Bar
}
}
static var Jar:Foo {
get {
return Foo(rawValue: 0)!
}
}
}
With recent version of Swift, this can be shortened like this:
enum Foo: Int {
case bar = 0
static var baz:Foo { .bar }
static var jar:Foo { Foo(rawValue: 0)! }
}
Note that Swift has changed their naming convention of enum variants from PascalCase to camelCase.
回答2:
Here is another way of getting around it:
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
}
}
}
回答3:
Swift doesn't allow elements of an enum
to share values. From the documentation on enums under the "Raw Values" heading (emphasis mine):
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.
回答4:
I'm not sure you can. The following is taken from Apple.
“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.”
Excerpt from: Apple Inc. “The Swift Programming Language.” iBooks. https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11
Since the enumeration members do not implicitly equal 0, 1, etc., the compiler looks at each as a unique value. When you try and duplicate it the compiler complains because it has already been created.
回答5:
I encounter the same problem about multiple case with the same value. After googling this problem, I prefer the following solution with computed properties
of enum
, which come from this post.
回答6:
I'm going prove that this is possible for all Swift versions
This is not a workaround. It is actually possible. Even though they say it isn't.
This truly does answer your question:
enum Foo: Bool {
case bar = 0
case bas = 1
case bat = 2
}
// THIS IS THE KEY!!
extension Bool: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) { self = true }
}
func bug() {
assert(Foo.bar == Foo.bas) // This assertion is legitimate!
assert(Foo.bar.rawValue == Foo.bas.rawValue) // This assertion is legitimate!
}
Incredibly, all the cases on the enum Foo
have a raw value of true
. You can definitely rejigger this to support more duplicate types than just Bool.
Just imagine all of the repercussions.. Set, Dictionary, switch statements, CaseIterable, etc.
I guess I'll have to submit a Swift Radar in bugs.swift.org... Or we can keep this our little secret
You can follow SR-13212 for updates on this bugfix.
Edit: - They're keeping this as a feature.
The Answer
You mentioned you wanted an enum with raw values of 0, 0, and 1
. Here it is!
enum Bar: Int {
case A = "0"
case B = "0 "
case C = "1"
}
extension Int: ExpressibleByStringLiteral {
public init(stringLiteral elements: String) {
self = Int(String(elements.first ?? Character("0"))) ?? 0
}
}
This extension takes the first character of each String and converts it to an Int. Therefore making the raw values truly 0, 0, and 1
.
True statement: Bar.A == Bar.B
It works currently on all Swift versions.
回答7:
I found this by sheer coincidence and was surprised there's no Swift-y dark magic in any of the answers (which are still good, of course). Personally I don't like the static
properties as much because they introduce overhead elsewhere, e.g. in the pattern matching.
I think the "cleanest" solution would be to rely on RawRepresentable
and define that yourself. Then you can counter the main reason why Swift doesn't support this out of the box: the init?(rawValue:)
method. By just specifying String as the adopted raw value (i.e. enum MyEnum: String { ... }
) the compiler can't generate the logic for that on its own. It would need to know which case to skip, as there are not "enough" potential raw values defined (since one or more are doubled). Adopting the protocol yourself allows you to simply chose one case as the "default" for a given raw value string and basically prevent the other one from constructed with a raw value (regular construction still works, of course).
Unfortunately that is also a bit of overhead, as the former case myCase = "its_raw_value"
then gets distributed over
- the regular case (without the raw value String):
case myCase
(the definition) - a switch in the
init?(rawValue:)
method:... case "its_raw_value": return .myCase ...
- a switch in the required
rawValue
property:... case .myCase: return "its_raw_value" ...
I think I have a nice way to reduce that a little and keep the raw values from floating around too much. I wrote a playground to illustrate and will simply paste it here:
// 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)
It should be self-explanatory. The case definition and assigning it its raw value is still split, I don't think there's a way around that, but it can come in handy. Of course all this requires the RawValue
be constraint and I rely on the enum be CaseIterable
to easily construct that helper dictionary, but I think that should be okay, performance wise.
回答8:
Disclaimer: I'm not recommending this. Most people would not consider best practice. There are downsides, depending on the application. Let's just say these ideas are theoretical, hypothetical, impure, just presented for contemplation
One possibility is to add some sort of meaningless differentiator sufficient to thwart Swift's check for identity that doesn't affect what you are doing with the raw values in your code.
He's an example with a Float
, where, in music, A♯ (A sharp) is the same frequency as B♭ (B flat). They are both different annotations for the same thing, in other words.
Differences measured in microHz are meaningless to audio engines, yet sufficient to differentiate case values in a Swift enumeration. In other words, practically speaking, there is no difference between 116.540 and 116.540001 in most music applications:
enum NoteFrequency : Float = {
case A = 110.0, A_SHARP = 116.540, B_FLAT = 116.540001
}
Going from quasi-tolerable to uglier: For strings that only get assigned and printed you might add a non-printable character to differentiate.
Obviously this has downsides (potentially serious), if you do something like this and later you (or someone else) might be unaware of what you did, and writes code that doesn't account for it (such as using in rawValue of an enumeration in String comparisons) and their code fails. To make this work in String comparisons you'd have to probably do something to ignore the last character or lop it the unprintable character with myEnumString.rawValue.dropLast()
before comparing...
enum Animals : String = {
case CAT = "Feline\u{01}", LION = "Feline\u{02}"
}
来源:https://stackoverflow.com/questions/28037772/enum-multiple-cases-with-the-same-value-in-swift