Accessing an Enumeration association value in Swift
Asked Answered
A

10

104

In this code I've written a really useless enum that defines a possible Number with Int or Float.

I can't understand how can I access the value that I set with the association. If I try to print it I get just (Enum Value)

enum Number {
    case int (Int)
    case float (Float)
}

let integer = Number.int(10)
let float = Number.float(10.5)
println("integer is \(integer)")
println("float is \(float)")
Affix answered 17/6, 2014 at 12:14 Comment(4)
@MikePollard It's not. They access the value only through switch.Affix
I'm assuming that's the only way to do it ...Ditchwater
@MikePollard it's a bit strange :P but probably it has sense for the nature of enumeration (and for its usage).Affix
Perhaps you want to write a function eg. fund getInt() -> Int? { switch self{ case .int(let n) : return n default: return nil ... Ditchwater
C
135

The value is associated to an instance of the enumeration. Therefore, to access it without a switch, you need to make a getter and make it available explicitly. Something like below:

enum Number {
    case int(Int)
    case float(Float)

    func get() -> NSNumber {
        switch self {
        case .int(let num):
            return num
        case .float(let num):
            return num
        }
    }
}

var vInteger = Number.int(10)
var vFloat = Number.float(10.5)

println(vInteger.get())
println(vFloat.get())

Maybe in the future something like that may be automatically created or a shorter convenience could be added to the language.

Crocket answered 17/6, 2014 at 15:2 Comment(1)
This is not scalable. Imagine you have 20 cases!Antoninus
F
148

For sake of completeness, enum's association value could be accesed also using if statement with pattern matching. Here is solution for original code:

enum Number {
  case int (Int)
  case float (Float)
}

let integer = Number.int(10)
let float = Number.float(10.5)

if case let .int(i) = integer {
  print("integer is \(i)")
}
if case let .float(f) = float {
  print("float is \(f)")
}

This solution is described in detail in: https://appventure.me/2015/10/17/advanced-practical-enum-examples/

Furr answered 11/5, 2016 at 10:26 Comment(1)
Alternate syntax: if case .int(let i) = integer { ... }Kim
C
135

The value is associated to an instance of the enumeration. Therefore, to access it without a switch, you need to make a getter and make it available explicitly. Something like below:

enum Number {
    case int(Int)
    case float(Float)

    func get() -> NSNumber {
        switch self {
        case .int(let num):
            return num
        case .float(let num):
            return num
        }
    }
}

var vInteger = Number.int(10)
var vFloat = Number.float(10.5)

println(vInteger.get())
println(vFloat.get())

Maybe in the future something like that may be automatically created or a shorter convenience could be added to the language.

Crocket answered 17/6, 2014 at 15:2 Comment(1)
This is not scalable. Imagine you have 20 cases!Antoninus
H
19

It surprises me that Swift 2 (as of beta 2) does not address this. Here's an example of a workaround approach for now:

enum TestAssociatedValue {
  case One(Int)
  case Two(String)
  case Three(AnyObject)

  func associatedValue() -> Any {
    switch self {
    case .One(let value):
      return value
    case .Two(let value):
      return value
    case .Three(let value):
      return value
    }
  }
}

let one = TestAssociatedValue.One(1)
let oneValue = one.associatedValue() // 1
let two = TestAssociatedValue.Two("two")
let twoValue = two.associatedValue() // two

class ThreeClass {
  let someValue = "Hello world!"
}

let three = TestMixed.Three(ThreeClass())
let threeValue = three. associatedValue() as! ThreeClass
print(threeValue.someValue)

If your enum mixes cases with and without associated values, you'll need to make the return type an optional. You could also return literals for some cases (that do not have associated values), mimicking raw-value typed enums. And you could even return the enum value itself for non-associated, non-raw-type cases. For example:

enum TestMixed {
  case One(Int)
  case Two(String)
  case Three(AnyObject)
  case Four
  case Five

  func value() -> Any? {
    switch self {
    case .One(let value):
      return value
    case .Two(let value):
      return value
    case .Three(let value):
      return value
    case .Four:
      return 4
    case .Five:
      return TestMixed.Five
    }
  }
}

let one = TestMixed.One(1)
let oneValue = one.value() // 1
let two = TestMixed.Two("two")
let twoValue = two.value() // two

class ThreeClass {
  let someValue = "Hello world!"
}

let three = TestMixed.Three(ThreeClass())
let threeValue = three.value() as! ThreeClass
print(threeValue.someValue)

let four = TestMixed.Four
let fourValue = four.value() // 4

let five = TestMixed.Five
let fiveValue = five.value() as! TestMixed

switch fiveValue {
case TestMixed.Five:
  print("It is")
default:
  print("It's not")
}
// Prints "It is"
Hypogenous answered 2/7, 2015 at 12:26 Comment(0)
A
8

I have used something like this:

switch number {
case .int(let n):
    println("integer is \(n)")
case .float(let n):
    println("float is \(n)")
}
Acerbate answered 17/6, 2014 at 12:21 Comment(6)
I'd like to access the value directly and not through a switch.Affix
@Affix - How can you access it without knowing what it is? As far as I know, the switch is the only way.Acerbate
@Affix - as manojids has pointed out, any attempt to access the associated values must be associated with a check for the enum case, so the switch really isn't adding any code, maybe moving it around a bit is all.Necktie
@Affix "How can you access it without knowing what it is? As far as I know, the switch is the only way. " - How about the if statement? It woudl be great to be able to test against one particular case and on match, extract the associated value.Hyalite
@NicolasMiari - I have added solution with if statement extraction: https://mcmap.net/q/138675/-accessing-an-enumeration-association-value-in-swiftFurr
Thank you. I didn't know that one.Hyalite
F
8

like @iQ. answer, you can use property in enum also

enum Number {
    case int (Int)
    var value: Int {
        switch self {
            case .int(let value):
                return value
        }
    }
}

let integer = Number.int(10)
println("integer is \(integer.value)")
Fem answered 17/6, 2014 at 15:7 Comment(0)
M
8

You can access enum associated value not only through switch! Mirrors come to our aid

Let's create a protocol

protocol MirrorAssociated {
    var associatedValues: [String: Any] { get }
}

extension MirrorAssociated {
    var associatedValues: [String: Any] {
        var values = [String: Any]()
        if let associated = Mirror(reflecting: self).children.first {
            let children = Mirror(reflecting: associated.value).children
            for case let item in children {
                if let label = item.label {
                    values[label] = item.value
                }
            }
        }
        return values
    }
}

and use it like this:

enum Test: MirrorAssociated {
    case test(value: String, anotherValue: Int)
}

Now we can access any associated value without using switch:

let test: Test = .test(value: "Test String", anotherValue: 1337)

if let value = test.associatedValues["value"] as? String {
    print("\(value)") // "Test String"
}

if let intValue = test.associatedValues["anotherValue"] as? Int {
    print("\(intValue)") // 1337
}
Marijo answered 1/10, 2021 at 13:20 Comment(3)
interesting solution. Better not do like this, but really interesting. Thanks for your answerNudd
Why not? It removed the need to implement a gazillion switch/case lines!Overunder
@Overunder because of mirroring is slow. Another problem of this solution is that you can miss in one letter in ["anotherValue"] and you will loose really lot of time to debug the code. To find reason of incorrect code behaviour will be really difficult task. ||||| If you want to ask someone - you need to write "@nickname" in another case this person will not see your message.Nudd
N
6
enum NumberEnum {
    case int(Int)
    case float(Float)
    case twoInts(Int, Int)
}

let someNum = NumberEnum.twoInts(10,10)

To get value from ValueAssociatedEnum you able to follow one of 3 ways:

Way 1:

if case .twoInts( let val1, let val2 ) = someNum {
    print("hello \(val1) \(val2)")
}

Way 2:

guard case let .int(val) = someNum else { return }
print(val)

Way 3:

switch someNum {
case .int(let val):
    print("\(val)")
default:
    break;
}

Bonus: Way 4 - Mirroring

Better do not use mirroring as

  1. it is slow. So code is bad for production.
  2. difficult to debug in case of some bugs will be there.

But you can check how to do value get using mirroring in answer of Ilia Kambarov: https://mcmap.net/q/138675/-accessing-an-enumeration-association-value-in-swift

Also interesting information about mirroring related to value-associated enums here: Generic enum associated value extension. Is it possible?

Nudd answered 31/3, 2023 at 23:45 Comment(0)
A
4

Swift 5

enum Directory {
    case accountImages(URL)
    case accountData(URL)
    
    var url: URL {
        switch self {
        case .accountImages(let url):
            return url
        case .accountData(let url):
            return url
        }
    }
}

func save(to directory: Directory) {
    let dir = directory.url
}
Ambrogio answered 4/2, 2022 at 19:25 Comment(0)
C
3

If you're using guard, you can write like below:

enum Action {
    case .moveTab(index: Int)
}

guard let case .moveTab(index) = someAction else { return }
Collapse answered 15/5, 2017 at 8:42 Comment(0)
B
0

Swift 4,

I have created a simple enum with associated values for handling firebase database reference paths

import Firebase

    enum FirebaseDBConstants  {

        case UserLocation(database : DatabaseReference, userID :String)
        case UserRequest(database : DatabaseReference, requestID :String)

        func getDBPath() -> DatabaseReference {
            switch self {
            case  .UserLocation(let database,let userID):
                return database.root.child(FirebaseDBEnvironmentEnum.getCurrentEnvioronMent()).child("Location").child(userID).child("JSON")

            case .UserRequest(let database,let requestID):
                return database.root.child(FirebaseDBEnvironmentEnum.getCurrentEnvioronMent()).child("Request").child(requestID)

            default:
                break
            }
        }
    }

Use it like as shown

//Pass Database refenence root as parameter with your request id
let dbPath = FirebaseDBConstants.UserRequest(database: database, requestID: requestId).getDBPath()
Beer answered 11/8, 2018 at 10:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.