Associated Value Enumerations
Associated values, allow us to combine a set of enumeration
cases and yet associate different sets of values of with each enumeration case.
This includes any number of values of any type we like as well as potentially
having no associated values at all. In Swift we can’t use both raw
values and associated values within the same enumeration at the same time.
Let’s
have a look at an example.
Here I’ve
defined an enumeration to represent colours in two different colour spaces):
enum ColorSpace {
case rgba(red: UInt8,
green: UInt8, blue: UInt8, alpha: Float)
case cmyk(cyan:
Float, magenta: Float, yellow: Float, black: Float)
}
There are a few things to take note of:
First, there is
no raw value type after the enumeration as we saw with raw values (i.e. no
colon followed by a type name after the enumeration name). This is because with
associated values, each enumeration case can have a different set of associated values and it therefore doesn’t really make
sense to specify a single type.
Secondly,
notice that the
.rgba
and .cmyk
cases have a different sets of values associated with them with
each set of associated values being defined as a comma separated list of label
/ type pairs between a pair of parentheses. The key point is that each
enumeration case only has the associated values that it needs.
Another thing
of note is that it is not actually a requirement that we supply labels for each
of the associated values. We could just as easily have written the enumeration
above as:
enum ColorSpace {
case rgba(UInt8,
UInt8, UInt8, Float)
case cmyk(Float,
Float, Float, Float)
}
Assigning Enumerations Values
with Associated Values
To
create an enumeration value with associated values, we must supply values for
each of the associated values of the enumeration at the point we assign the
enumeration value:
var color1 = Color.rbga(red: 100, green: 100, blue: 100, alpha:1.0)
var color2 = Color.cmyk(cyan: 0.5, magenta:
0.5, yellow: 0.5, black: 0.5)
Enumerations and Pattern Matching
For simple enumerations and enumerations with raw values, we
can use a simple enumeration
case pattern to match
individual enumeration cases:
enum DayOfWeek {
case Monday, Tuesday,
Wednesday, Thursday, Friday, Saturday, Sunday
}
let day = DayOfWeek.Monday
switch day {
case .Monday: print("The
Moon's day")
case .Tuesday: print("The
Norse god Tyr")
case .Wednesday:
print("The Norse god Odin")
case .Thursday:
print("The Norse god Thor")
case .Friday: print("The
Norse god Frigg")
case .Saturday:
print("Saturn's Day")
case .Sunday: print("The
Sun's Day")
}
// prints "The Moon's day"
When it comes to enumeration cases with associated values, we
also have the option of using the value-binding
pattern to match and extract enumeration cases and
their associated values:
enum Day {
case Monday(units:
Int)
case Tuesday(units:
Int)
case Wednesday(units:
Int)
case Thursday(units:
Int)
case Friday(units:
Int)
case Saturday(units:
Int)
case Sunday(units:
Int)
}
let sales = Day.Monday(units: 42)
switch sales {
case let .Monday(units):
print("Sold
\(units) on Monday")
case let .Tuesday(units):
print("Sold
\(units) on Tuesday")
// ...
default:
print("Not
sure about the rest of the week!")
}
Enumeration Equality
Enumerations checking for equality is relatively straight
forward:
enum Planet {
case Mercury, Venus,
Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto
}
var planet = Planet.Mercury
if planet == .Earth {
print("The
Blue Marble")
}
This also works for enumerations with raw values:
enum Planet : Int {
case Mercury = 1,
Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto
}
var planet = Saturn
if planet == .Saturn {
print("The
Ringed Planet")
}
Boolean Comparison of Associated
Values Enumerations
This is little difficult because Swift doesn’t know how to
compare the different associated values for each enumeration case to determine
whether two cases are equal or not. To fix this, we have to give Swift a bit of
help. This means implementing the equality operator (
==
) for the enumeration type ourselves.
For example:
enum UserAction {
case Start
case Pause
case Stop
case Restart(delay:
Int)
}
func ==(lhs: UserAction, rhs: UserAction)
-> Bool {
switch (lhs, rhs)
{
case let (.Restart(delay1),
.Restart(delay2)):
return
delay1 == delay2
case (.Start, .Start),
(.Pause, .Pause), (.Stop, .Stop):
return
true
default:
return
false
}
}
UserAction.Start == UserAction.Start //
true
UserAction.Start == UserAction.Restart(delay:10)
// false
UserAction.Restart(delay: 10) == UserAction.Restart(delay:12)
// false
UserAction.Restart(delay: 10) == UserAction.Restart(delay:
10) // true
Recursive Enumerations
It’s
probably easier to explain recursive enumerations with an example. Imagine we
wanted to represent a binary tree within our code.
We
could model the binary tree using an enumeration as follows:
enum BinaryTree<T> {
case leaf(T)
case node(leftChild:
BinaryTree, rightChild: BinaryTree?)
}
The key point here is that the second case of the enumeration
has up to two associated values which themselves are also of the same type as
enumeration type that is being defined (
BinaryTree<T>
).
The other thing
with this example is that, as written, it doesn’t actually compile.
If we put this into
a playground, Xcode displays the following error
Recursive
enum ‘BinaryTree<T>’ is not marked as ‘indirect’.
Recursive
definitions like the one written above aren’t actually allowed.Instead, we have
to tell Swift to modify the way that it stores the associated values of the
enumeration by including the
indirect
keyword.
enum BinaryTree<T> {
case leaf(T)
indirect case node(left:
BinaryTree, right: BinaryTree?)
}
This modifies the storage of just those cases only.
indirect enum BinaryTree<T> {
case leaf(T)
case node(left:
BinaryTree, right: BinaryTree?)
}
This will modify the
storage of all cases with associated values
Nested Enumerations
Nested
enumerations are enumerations that are defined within the body of another
enumeration. The main reason for defining nested
enumerations is so we can group together enumerations that are related, for
example where one enumeration is used as the associated value of one or more
cases in another enumeration.
enum TyreType {
enum TyreColor
{
case
purple // UltraSoft
case
red // SuperSoft
case
yellow // Soft
case
white // Medium
case
orange // Hard
case
green // Intermediate
case
blue // FullWet
}
case
ultraSoft(color: TyreColor)
case
superSoft(color: TyreColor)
case
soft(color: TyreColor)
case
medium(color: TyreColor)
case
hard(color: TyreColor)
case
intermediate(color: TyreColor, litresClearedPerSecond: UInt8)
case
fullWet(color: TyreColor, litresClearedPerSecond: UInt8)
}
let qualificationTyre =
TyreType.ultraSoft(color: .purple)
let raceTyre =
TyreType.intermediate(color:.green, litresClearedPerSecond: 25)
Contained Enumerations
We
can contain enumerations within other structured types such as
classes or structs.
For example
struct F1RacingCar {
enum RaceTeam {
case
mercedes
case
ferrari
case
redBullRacing
case
williams
case
toroRosso
case
mcLaren
case
forceIndia
case
renault
case
sauber
case
haasF1
case
manorRacing
}
enum TyreType {
case
ultraSoft
case
superSoft
case
soft
case
medium
case
hard
case
intermediate
case
fullWet
}
let team: RaceTeam
let tyre: TyreType
}
let racingCar = F1RacingCar(team: .mercedes,
tyre: .superSoft)