12

enum have a property named 'hashValue' which is its index inside the enum.

Now my question is, is it possible to access its value by using a number? Ex: let variable:AnEnum = 0

Cœur
  • 37,241
  • 25
  • 195
  • 267
Arbitur
  • 38,684
  • 22
  • 91
  • 128
  • 7
    hashValue is only the index from Xcode 6 to Xcode 9. In Xcode 10 builds, hashValue is unrelated to the index. In other words: this question has lost its value. – Cœur Sep 17 '18 at 14:28
  • From doc, it's clear that you can't rely on hash values for being indexes: _"Hash values are not guaranteed to be equal across different executions of your program."_ – Cœur Sep 17 '18 at 14:34

6 Answers6

15

If you want to map enum values to integers, you should do so directly with raw values. For example (from the Swift Programming Language: Enumerations):

enum Planet: Int {
    case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}

let possiblePlanet = Planet(rawValue: 7)

I don't believe there's any documentation promising that an enum's hashValue is anything in particular (if you have a link, I've be very interested). In the absence of that, you should be explicit in your assignment of raw values.

Dan Beaulieu
  • 19,406
  • 19
  • 101
  • 135
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • My enum is an enum of CGPoints. – Arbitur Sep 27 '14 at 17:47
  • What is an "enum of CGPoints"? – Rob Napier Sep 27 '14 at 17:48
  • enum AnEnum:CGPoint {}, sorry for my poor wordchoises, I meant to say "My enum is an enum of type CGPoint" ;) – Arbitur Sep 27 '14 at 17:49
  • Does that even compile? You should get "Raw type 'CGPoint' is not convertible from any literal." – Rob Napier Sep 27 '14 at 17:49
  • Im using the protocol "StringLiteralConvertible" as an extension of CGPoint and in the enum the CGPoints are strings. – Arbitur Sep 27 '14 at 17:50
  • 3
    Please don't... this is really overloading enums too far IMO and is likely to create a lot of strange compiler errors in the edges cases. But if you are boxed into it, then implement `static func fromRaw(x: Int) -> AnEnum?` to do what you want it to do. `hashValue` promises nothing. – Rob Napier Sep 27 '14 at 17:55
  • (Note that fromRaw and toRaw change in 6.1: http://adcdownload.apple.com//Developer_Tools/xcode_6.1_beta_2_c9kln6/xcode_6.1_beta_2_release_notes.pdf. The new way is with init?. But the concept is the same. – Rob Napier Sep 27 '14 at 18:01
  • fyi, I've updated to the latest swift syntax. I wanted to notify in case this conflicts with your intent. – Dan Beaulieu Oct 07 '15 at 13:40
10
enum Opponent: String {
   case Player
   case Computer

   static func fromHashValue(hashValue: Int) -> Opponent {
       if hashValue == 0 {
           return .Player
       } else {
           return .Computer
       }
   }

}

Explanation:

Since there is no way to get back an enum value from its hashValue, you have to do it manually. It's not pretty, but it works. You essentially create a function that allows you to pass in the index of the value you want and manually return that enum value back to the caller. This could get nasty with an enum with tons of cases, but it works like a charm for me.

Stephen Paul
  • 2,762
  • 2
  • 21
  • 25
  • 1
    Some explanatory text could turn this code block into a good answer. – Mogsdad Apr 19 '16 at 01:34
  • @Mogsdad You could've just asked me to expound on it without docking me a point. – Stephen Paul Apr 19 '16 at 04:44
  • @Mogsdad Cool. Explanation added. – Stephen Paul Apr 25 '16 at 20:16
  • `= "Player"` is redundant – Leo Dabus Apr 28 '16 at 19:19
  • @LeoDabus It's just an example of getting an enum value based off of its hashValue. "= 'Player'" being redundant isn't my intention. BTW, if you'd like to discuss why it's *not* redundant and why I'm even doing this, feel free to message me. – Stephen Paul Apr 28 '16 at 20:20
  • 1
    check apple doc Implicitly Assigned Raw Values example `enum CompassPoint: String {` https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-XID_228 – Leo Dabus Apr 28 '16 at 21:25
  • 1
    @LeoDabus Well I guess I learn something new every day. I can't thank you enough for pointing this out to me. +1 – Stephen Paul Apr 29 '16 at 07:15
  • 1
    @StephenPaul you can also extend your enumeration and create an initializer https://www.dropbox.com/sh/70r4vzs3olmh75g/AAAszHbqmaVDnVh6LraD6Pf4a?dl=1 – Leo Dabus Apr 29 '16 at 17:51
  • 1
    @LeoDabus That looks great. I will update my answer to reflect your suggestion. – Stephen Paul Apr 30 '16 at 00:30
1

Swift 4, iOS 12:

Simply make your enum with explicitly setting raw type (like Int in below example):

enum OrderStatus: Int {
    case noOrder
    case orderInProgress
    case orderCompleted
    case orderCancelled
}

Usage:

var orderStatus: OrderStatus = .noOrder // default value 

print(orderStatus.rawValue) // it will print 0

orderStatus = .orderCompleted
print(orderStatus.rawValue) // it will print 2
Irfan Anwar
  • 1,878
  • 17
  • 30
0

Swift 4.2 (Based on previous answer by @Stephen paul)

This answer uses switch instead of if/else clauses. And returns optional as your not garantueed that the hash provided will match.

enum CellType:String{
    case primary,secondary,tierary
    /**
     * NOTE: Since there is no way to get back an enum value from its hashValue, you have to do it manually.
     * EXAMPLE: CellType.fromHashValue(hashValue: 1)?.rawValue//primary
     */
    static func fromHashValue(hashValue: Int) -> CellType? {
        switch hashValue {
        case 0:
            return .primary
        case 1:
            return .secondary
        case 2:
            return .tierary
        default:
            return nil
        }
    }
}
Sentry.co
  • 5,355
  • 43
  • 38
0

Your requirement is to have this line of code working, where 0 is the hashValue of the enum variable (note that starting with Xcode 10, 0 is never a valid hashValue...):

let variable:AnEnum = 0

This is simply done by making your enum ExpressibleByIntegerLiteral and CaseIterable:

extension AnEnum: CaseIterable, ExpressibleByIntegerLiteral {
    typealias IntegerLiteralType = Int

    public init(integerLiteral value: IntegerLiteralType) {
        self = AnEnum.allCases.first { $0.hashValue == value }!
    }
}

The CaseIterable protocol is natively available with Swift 4.2, and you can implement it yourself for older swift versions (Swift 3.x, 4.x) using the code from https://stackoverflow.com/a/49588446/1033581.

Cœur
  • 37,241
  • 25
  • 195
  • 267
0

Actually, with Swift 4.2, the hashValue is not the index inside the enum anymore.

Edit: Just found a safer way to achieve that. You can use the CaseIterable (Swift 4.2) and pick the desired case in the allCases collection.

enum Foo: CaseIterable {
  case bar
  case baz

  init?(withIndex index: Int) {
    guard Foo.allCases.indices ~= index else { return nil }
    self = Foo.allCases[index]
  }
}

Foo(withIndex: 0) // bar
Foo(withIndex: 1) // baz
Foo(withIndex: 2) // nil

Note: I'm leaving this little trick here, because playing with unsafe pointer is fun, but please, do not use this method to create a case with an index. It relies on Swift memory representation which might change without notice, and is really unsafe because using a wrong index produce a runtime error.

That being said, in Swift, a case is represented using an Int in raw memory. So you can use this to build a case using unsafe pointers.

enum Foo {
  case bar
  case baz

  init(withIndex index: UInt8) {
    var temp: Foo = .bar
    withUnsafeMutablePointer(to: &temp) { pointer in
      let ptr = UnsafeMutableRawPointer(pointer).bindMemory(to: UInt8.self, capacity: 1)
      ptr.pointee = index
    }

    self = temp
  }
}

Foo(withIndex: 0) // bar
Foo(withIndex: 1) // baz
Foo(withIndex: 2) // runtime error !
rraphael
  • 10,041
  • 2
  • 25
  • 33