Enums in Swift
Basic enumerations¶
An enum provides a set of related values:
or
Enum values can be used by their fully-qualified name, but you can omit the type name when it can be inferred:
The most fundamental way of comparing/extracting enum values is with a switch statement:
let dir: Direction = .up
switch dir {
case .up:
print("Up")
case .down:
print("Down")
case .left:
print("Left")
case .right:
print("Right")
}
// Output: Up
Simple enums are automatically Hashable, Equatable and have string conversions:
let dir: Direction = .up
if dir == .down {
// perform some action
}
let dirs: Set<Direction> = [.right, .left]
print(Direction.up) // Output: up
debugPrint(Direction.up) // Output: Direction.up
Enums with associated values¶
Enum cases can contain one or more payloads (associated values):
// The "move" case has an associated distance
enum Action {
case jump
case kick
case move(distance: Float)
}
The payload must be provided when instantiating the enum value:
performAction(.jump)
performAction(.kick)
performAction(.move(distance: 3.3))
performAction(.move(distance: 0.5))
func performAction(_ action: Action) {
print(action)
}
The switch statement can extract the associated value:
switch action {
case .jump:
break
case .kick:
break
case .move(let distance):
print("Moving: \(distance)")
}
// Output: Moving: 5.0
A single case extraction can be done using if case:
The guard case syntax can be used for later use extraction:
Enums with associated values are not Equatable by default. Implementation of the == operator must be done manually:
extension Action: Equatable {}
func == (lhs: Action, rhs: Action) -> Bool {
switch lhs {
case .jump:
if case .jump = rhs { return true }
case .kick:
if case .kick = rhs { return true }
case .move(let lhsDistance):
if case .move (let rhsDistance) = rhs {
return lhsDistance == rhsDistance
}
}
return false
}
Indirect payloads¶
Normally, enums can't be recursive (because they would require infinite storage):
enum Tree<T> {
case leaf(T)
case branch(Tree<T>, Tree<T>)
}
// → error: recursive enum 'Tree<T>' is not marked 'indirect'
The indirect keyword makes the enum store its payload with a layer of indirection, rather than storing it inline. You can use this keyword on a single case:
enum Tree<T> {
case leaf(T)
indirect case branch(Tree<T>, Tree<T>)
}
let tree = Tree.branch(.leaf(1), .branch(.leaf(2), .leaf(3)))
indirect also works on the whole enum, making any case indirect when necessary:
Raw Values¶
Enums without payloads can have raw values of any literal type:
enum Rotation: Int {
case up = 0
case left = 90
case upsideDown = 180
case right = 270
}
let rotation = Rotation.up
print(rotation.rawValue) // Output: 0
Enums without any specific type do not expose the rawValue property:
enum Rotation {
case up
case right
case down
case left
}
let rotation = Rotation.up
print(rotation.rawValue)
// → error: value of type 'Rotation' has no member 'rawValue'
Integer raw values are assumed to start at 0 and increase monotonically:
enum MetasyntacticVariable: Int {
case foo // → rawValue = 0
case bar // → rawValue = 1
case baz = 7
case quux // → rawValue = 8
}
String raw values can be synthesized automatically:
A raw-valued enum automatically conforms to RawRepresentable. You can get an enum value's corresponding raw value with .rawValue:
You can also create an enum from a raw value using init?(rawValue:):
enum Rotation: Int {
case up = 0
case left = 90
case upsideDown = 180
case right = 270
}
let rotation = Rotation(rawValue: 0)
print(rotation) // Output: Optional(Rotation.Up)
let otherRotation = Rotation(rawValue: 45)
// → otherRotation = nil (there is no Rotation with rawValue 45)
print(otherRotation) // Output: nil
Initializers¶
Enums can have custom init methods that can be more useful than the default init?(rawValue:). Enums can also store values as well. This can be useful for storing the values they where initialized with and retrieving that value later.
enum CompassDirection {
case north(Int)
case south(Int)
case east(Int)
case west(Int)
}
extension CompassDirection {
init?(degrees: Int) {
switch degrees {
case 0...45:
self = .north(degrees)
case 46...135:
self = .east(degrees)
case 136...225:
self = .south(degrees)
case 226...315:
self = .west(degrees)
case 316...360:
self = .north(degrees)
default:
return nil
}
}
var value: Int {
switch self {
case .north(let degrees):
return degrees
case .south(let degrees):
return degrees
case .east(let degrees):
return degrees
case .west(let degrees):
return degrees
}
}
}
Using that initializer we can do this:
var direction = CompassDirection(degrees: 90)
// → direction = Optional(CompassDirection.east)
print(direction?.value) // Output: Optional(90)
direction = CompassDirection(degrees: 500)
// → direction = nil
print(direction?.value) // Output: nil
Nested Enumerations¶
You can nest enumerations one inside an other, this allows you to structure hierarchical enums to be more organized and clear.
enum Orchestra {
enum Strings {
case violin
case viola
case cello
case doubleBase
}
enum Keyboards {
case piano
case celesta
case harp
}
enum Woodwinds {
case flute
case oboe
case clarinet
case bassoon
case contrabassoon
}
}
And you can use it like that:
Bonus¶
Enums in Swift are much more powerful than some of their counterparts in other languages, such as C. They share many features with classes and structs, such as defining initialisers, computed properties, instance methods, protocol conformance and extensions.
protocol ChangesDirection {
mutating func changeDirection()
}
enum Direction {
// enumeration cases
case up, down, left, right
// initialise the enum instance with a case
// that's in the opposite direction to another
init(oppositeTo otherDirection: Direction) {
self = otherDirection.opposite
}
// computed property that returns the opposite direction
var opposite: Direction {
switch self {
case .up:
return .down
case .down:
return .up
case .left:
return .right
case .right:
return .left
}
}
}
// extension to Direction that adds conformance
// to the ChangesDirection protocol
extension Direction: ChangesDirection {
mutating func changeDirection() {
self = .left
}
}
var dir = Direction(oppositeTo: .down)
// → dir = Direction.up
dir.changeDirection()
// → dir = Direction.left
let opposite = dir.opposite
// → dir = Direction.right
debugPrint(dir) // Output: Direction.left
debugPrint(opposite) // Output: Direction.right