How to enumerate an enum with String type?
enum Suit: String {
case spades = "♠"
case hearts = "♥"
case diamonds = "♦"
case clubs = "♣"
}
For example, how can I do something like:
for suit in Suit {
// do something with suit
print(suit.rawValue)
}
Resulting example:
♠
♥
♦
♣
This post is relevant here https://www.swift-studies.com/blog/2014/6/10/enumerating-enums-in-swift
Essentially the proposed solution is
enum ProductCategory : String {
case Washers = "washers", Dryers = "dryers", Toasters = "toasters"
static let allValues = [Washers, Dryers, Toasters]
}
for category in ProductCategory.allValues{
//Do something
}
Swift 4.2+
Starting with Swift 4.2 (with Xcode 10), just add protocol conformance to CaseIterable
to benefit from allCases
. To add this protocol conformance, you simply need to write somewhere:
extension Suit: CaseIterable {}
If the enum is your own, you may specify the conformance directly in the declaration:
enum Suit: String, CaseIterable { case spades = "♠"; case hearts = "♥"; case diamonds = "♦"; case clubs = "♣" }
Then the following code will print all possible values:
Suit.allCases.forEach {
print($0.rawValue)
}
Compatibility with earlier Swift versions (3.x and 4.x)
If you need to support Swift 3.x or 4.0, you may mimic the Swift 4.2 implementation by adding the following code:
#if !swift(>=4.2)
public protocol CaseIterable {
associatedtype AllCases: Collection where AllCases.Element == Self
static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
static var allCases: [Self] {
return [Self](AnySequence { () -> AnyIterator<Self> in
var raw = 0
var first: Self?
return AnyIterator {
let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
if raw == 0 {
first = current
} else if current == first {
return nil
}
raw += 1
return current
}
})
}
}
#endif
I made a utility function iterateEnum()
for iterating cases for arbitrary enum
types.
Here is the example usage:
enum Suit: String {
case Spades = "♠"
case Hearts = "♥"
case Diamonds = "♦"
case Clubs = "♣"
}
for f in iterateEnum(Suit) {
println(f.rawValue)
}
Which outputs:
♠
♥
♦
♣
But, this is only for debug or test purposes: This relies on several undocumented Swift1.1 compiler behaviors, so, use it at your own risk.
Here is the code:
func iterateEnum<T: Hashable>(_: T.Type) -> GeneratorOf<T> {
var cast: (Int -> T)!
switch sizeof(T) {
case 0: return GeneratorOf(GeneratorOfOne(unsafeBitCast((), T.self)))
case 1: cast = { unsafeBitCast(UInt8(truncatingBitPattern: $0), T.self) }
case 2: cast = { unsafeBitCast(UInt16(truncatingBitPattern: $0), T.self) }
case 4: cast = { unsafeBitCast(UInt32(truncatingBitPattern: $0), T.self) }
case 8: cast = { unsafeBitCast(UInt64($0), T.self) }
default: fatalError("cannot be here")
}
var i = 0
return GeneratorOf {
let next = cast(i)
return next.hashValue == i++ ? next : nil
}
}
The underlying idea is:
- Memory representation of
enum
, excludingenum
s with associated types, is just an index of cases when the count of the cases is2...256
, it's identical toUInt8
, when257...65536
, it'sUInt16
and so on. So, it can beunsafeBitcast
from corresponding unsigned integer types. -
.hashValue
of enum values is the same as the index of the case. -
.hashValue
of enum values bitcasted from invalid index is0
.
Revised for Swift2 and implemented casting ideas from @Kametrixom's answer:
func iterateEnum<T: Hashable>(_: T.Type) -> AnyGenerator<T> {
var i = 0
return anyGenerator {
let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
return next.hashValue == i++ ? next : nil
}
}
Revised for Swift3:
func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
var i = 0
return AnyIterator {
let next = withUnsafePointer(to: &i) {
$0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee }
}
if next.hashValue != i { return nil }
i += 1
return next
}
}
Revised for Swift3.0.1:
func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
var i = 0
return AnyIterator {
let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) }
if next.hashValue != i { return nil }
i += 1
return next
}
}