Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enum variables in Swift?

Tags:

enums

swift

I would like to associate multiple values with an enum value, in a generic way.

This can be done in Java:

enum Test {

    A("test", 2);

    final String var1;
    final int var2;

    Test (String var1, int var2) {
        this.var1 = var1;
        this.var2 = var2;
    }
}

 public static void main(String []args){
    Test test = Test.A;
    System.out.println(test.var1);
 }

But it looks like it's not possible with Swift? So far, according to docs, there are:

  1. Associated values. Example (from docs):

    enum Barcode {
        case UPCA(Int, Int, Int, Int)
        case QRCode(String)
    }
    

    But this is not what I need.

  2. Raw value. Example (from docs):

    enum ASCIIControlCharacter: Character {
        case Tab = "\t"
        case LineFeed = "\n"
        case CarriageReturn = "\r"
    }
    

    This would be what I need, but it can have only one value!

Is there an elegant solution for this...? Seems like a language design decision, as it would conflict with the associated values concept, at least in the current form. I know I could use e.g. a dictionary to map the enum values to the rest, but really missing to do this in one safe step, like in Java.

like image 801
User Avatar asked Feb 08 '15 12:02

User


1 Answers

For Swift enum, you can only use (String|Integer|Float)LiteralConvertible types as the raw value. If you want to use existing type(e.g. CGPoint) for the raw value, you should follow @Alex answer.

I will provide 2 alternatives in this answer

Very simple solution

enum Test: String {
    case A = "foo:1"
    case B = "bar:2"

    var var1: String {
        return split(self.rawValue, { $0 == ":" })[0]
    }
    var var2: Int {
        return split(self.rawValue, { $0 == ":" })[1].toInt()!
    }
}

let test = Test.A
println(test.var1) // -> "foo"

You don't like this? go to next one :)

Behavior emulation using struct and static constants

struct Test {
    let var1: String
    let var2: Int
    private init(_ var1:String, _ var2:Int) {
        self.var1 = var1
        self.var2 = var2
    }
}

extension Test {
    static let A = Test("foo", 1)
    static let B = Test("bar", 2)
    static let allValues = [A, B]
}

let test = Test.A
println(test.var1) // -> "foo"

But of course, struct lacks some features from enum. You have to manually implement it.

Swift enum implicitly conforms Hashable protocol.

extension Test: Hashable {
    var hashValue:Int {
        return find(Test.allValues, self)!
    }
}

func ==(lhs:Test, rhs:Test) -> Bool {
    return lhs.var1 == rhs.var1 && lhs.var2 == rhs.var2
}

Test.A.hashValue // -> 0
Test.B.hashValue // -> 1
Test.A == Test.B // -> false

In the first code, we already have allValues that is corresponding to values() in Java. valueOf(...) in Java is equivalent to init?(rawValue:) in RawRepresentable protocol in Swift:

extension Test: RawRepresentable {

    typealias RawValue = (String, Int)

    init?(rawValue: RawValue) {
        self.init(rawValue)
        if find(Test.allValues, self) == nil{
            return nil
        }
    }

    var rawValue: RawValue {
        return (var1, var2)
    }
}

Test(rawValue: ("bar", 2)) == Test.B
Test(rawValue: ("bar", 4)) == nil

And so on...

I know this is not "in a generic way". And one thing we never can emulate is "Matching Enumeration Values with a Switch Statement" feature in Swift. you always need default case:

var test = Test.A
switch test {
case Test.A: println("is A")
case Test.B: println("is B")
default: fatalError("cannot be here!")
}
like image 198
rintaro Avatar answered Oct 21 '22 17:10

rintaro