Files
gh-kylehughes-the-unofficia…/skills/programming-swift/LanguageGuide/Enumerations.md
2025-11-30 08:36:15 +08:00

866 lines
24 KiB
Markdown

# Enumerations
Model custom types that define a list of possible values.
An *enumeration* defines a common type for a group of related values
and enables you to work with those values in a type-safe way within your code.
If you are familiar with C,
you will know that C enumerations assign related names to a set of integer values.
Enumerations in Swift are much more flexible,
and don't have to provide a value for each case of the enumeration.
If a value (known as a *raw* value) is provided for each enumeration case,
the value can be a string, a character,
or a value of any integer or floating-point type.
Alternatively, enumeration cases can specify
associated values of *any* type to be stored along with each different case value,
much as unions or variants do in other languages.
You can define a common set of related cases as part of one enumeration,
each of which has a different set of values of appropriate types associated with it.
Enumerations in Swift are first-class types in their own right.
They adopt many features traditionally supported only by classes,
such as computed properties to provide additional information about
the enumeration's current value,
and instance methods to provide functionality related to
the values the enumeration represents.
Enumerations can also define initializers to provide an initial case value;
can be extended to expand their functionality beyond their original implementation;
and can conform to protocols to provide standard functionality.
For more about these capabilities, see
<doc:Properties>, <doc:Methods>, <doc:Initialization>,
<doc:Extensions>, and <doc:Protocols>.
<!--
TODO: this chapter should probably mention that enums without associated values
are hashable and equatable by default (and what that means in practice)
-->
## Enumeration Syntax
You introduce enumerations with the `enum` keyword
and place their entire definition within a pair of braces:
```swift
enum SomeEnumeration {
// enumeration definition goes here
}
```
<!--
- test: `enums`
```swifttest
-> enum SomeEnumeration {
// enumeration definition goes here
}
```
-->
Here's an example for the four main points of a compass:
```swift
enum CompassPoint {
case north
case south
case east
case west
}
```
<!--
- test: `enums`
```swifttest
-> enum CompassPoint {
case north
case south
case east
case west
}
```
-->
The values defined in an enumeration
(such as `north`, `south`, `east`, and `west`)
are its *enumeration cases*.
You use the `case` keyword to introduce new enumeration cases.
> Note: Swift enumeration cases don't have an integer value set by default,
> unlike languages like C and Objective-C.
> In the `CompassPoint` example above,
> `north`, `south`, `east` and `west`
> don't implicitly equal
> `0`, `1`, `2` and `3`.
> Instead, the different enumeration cases are values in their own right,
> with an explicitly defined type of `CompassPoint`.
Multiple cases can appear on a single line, separated by commas:
```swift
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
```
<!--
- test: `enums`
```swifttest
-> enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
```
-->
Each enumeration definition defines a new type.
Like other types in Swift, their names
(such as `CompassPoint` and `Planet`)
start with a capital letter.
Give enumeration types singular rather than plural names,
so that they read as self-evident:
```swift
var directionToHead = CompassPoint.west
```
<!--
- test: `enums`
```swifttest
-> var directionToHead = CompassPoint.west
```
-->
The type of `directionToHead` is inferred
when it's initialized with one of the possible values of `CompassPoint`.
Once `directionToHead` is declared as a `CompassPoint`,
you can set it to a different `CompassPoint` value using a shorter dot syntax:
```swift
directionToHead = .east
```
<!--
- test: `enums`
```swifttest
-> directionToHead = .east
```
-->
The type of `directionToHead` is already known,
and so you can drop the type when setting its value.
This makes for highly readable code when working with explicitly typed enumeration values.
## Matching Enumeration Values with a Switch Statement
You can match individual enumeration values with a `switch` statement:
```swift
directionToHead = .south
switch directionToHead {
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
// Prints "Watch out for penguins".
```
<!--
- test: `enums`
```swifttest
-> directionToHead = .south
-> switch directionToHead {
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
<- Watch out for penguins
```
-->
You can read this code as:
“Consider the value of `directionToHead`.
In the case where it equals `.north`,
print `"Lots of planets have a north"`.
In the case where it equals `.south`,
print `"Watch out for penguins"`.”
…and so on.
As described in <doc:ControlFlow>,
a `switch` statement must be exhaustive when considering an enumeration's cases.
If the `case` for `.west` is omitted,
this code doesn't compile,
because it doesn't consider the complete list of `CompassPoint` cases.
Requiring exhaustiveness ensures that enumeration cases aren't accidentally omitted.
When it isn't appropriate to provide a `case` for every enumeration case,
you can provide a `default` case to cover any cases that aren't addressed explicitly:
```swift
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// Prints "Mostly harmless".
```
<!--
- test: `enums`
```swifttest
-> let somePlanet = Planet.earth
-> switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
<- Mostly harmless
```
-->
## Iterating over Enumeration Cases
For some enumerations,
it's useful to have a collection of all of that enumeration's cases.
You enable this by
writing `: CaseIterable` after the enumeration's name.
Swift exposes a collection of all the cases
as an `allCases` property of the enumeration type.
Here's an example:
```swift
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available".
```
<!--
- test: `enums`
```swifttest
-> enum Beverage: CaseIterable {
case coffee, tea, juice
}
-> let numberOfChoices = Beverage.allCases.count
-> print("\(numberOfChoices) beverages available")
<- 3 beverages available
```
-->
In the example above,
you write `Beverage.allCases` to access a collection
that contains all of the cases of the `Beverage` enumeration.
You can use `allCases` like any other collection ---
the collection's elements are instances of the enumeration type,
so in this case they're `Beverage` values.
The example above counts how many cases there are,
and the example below uses a `for`-`in` loop to iterate over all the cases.
```swift
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice
```
<!--
- test: `enums`
```swifttest
-> for beverage in Beverage.allCases {
print(beverage)
}
<< coffee
<< tea
<< juice
// coffee
// tea
// juice
```
-->
The syntax used in the examples above
marks the enumeration as conforming to the
[`CaseIterable`](https://developer.apple.com/documentation/swift/caseiterable) protocol.
For information about protocols, see <doc:Protocols>.
## Associated Values
The examples in the previous section show how the cases of an enumeration are
a defined (and typed) value in their own right.
You can set a constant or variable to `Planet.earth`,
and check for this value later.
However, it's sometimes useful to be able to store
values of other types alongside these case values.
This additional information is called an *associated value*,
and it varies each time you use that case as a value in your code.
You can define Swift enumerations to store associated values of any given type,
and the value types can be different for each case of the enumeration if needed.
Enumerations similar to these are known as
*discriminated unions*, *tagged unions*, or *variants*
in other programming languages.
For example, suppose an inventory tracking system needs to
track products by two different types of barcode.
Some products are labeled with 1D barcodes in UPC format,
which uses the numbers `0` to `9`.
Each barcode has a number system digit,
followed by five manufacturer code digits and five product code digits.
These are followed by a check digit to verify that the code has been scanned correctly:
![](barcode_UPC)
Other products are labeled with 2D barcodes in QR code format,
which can use any ISO 8859-1 character
and can encode a string up to 2,953 characters long:
![](barcode_QR)
It's convenient for an inventory tracking system to store UPC barcodes
as a tuple of four integers,
and QR code barcodes as a string of any length.
In Swift, an enumeration to define product barcodes of either type might look like this:
```swift
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
```
<!--
- test: `enums`
```swifttest
-> enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
```
-->
This can be read as:
“Define an enumeration type called `Barcode`,
which can take either a value of `upc`
with an associated value of type (`Int`, `Int`, `Int`, `Int`),
or a value of `qrCode` with an associated value of type `String`.”
This definition doesn't provide any actual `Int` or `String` values ---
it just defines the *type* of associated values
that `Barcode` constants and variables can store
when they're equal to `Barcode.upc` or `Barcode.qrCode`.
You can then create new barcodes using either type:
```swift
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
```
<!--
- test: `enums`
```swifttest
-> var productBarcode = Barcode.upc(8, 85909, 51226, 3)
```
-->
This example creates a new variable called `productBarcode`
and assigns it a value of `Barcode.upc`
with an associated tuple value of `(8, 85909, 51226, 3)`.
You can assign the same product a different type of barcode:
```swift
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
```
<!--
- test: `enums`
```swifttest
-> productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
```
-->
At this point,
the original `Barcode.upc` and its integer values are replaced by
the new `Barcode.qrCode` and its string value.
Constants and variables of type `Barcode` can store either a `.upc` or a `.qrCode`
(together with their associated values),
but they can store only one of them at any given time.
You can check the different barcode types using a switch statement,
similar to the example in
<doc:Enumerations#Matching-Enumeration-Values-with-a-Switch-Statement>.
This time, however,
the associated values are extracted as part of the switch statement.
You extract each associated value as a constant (with the `let` prefix)
or a variable (with the `var` prefix)
for use within the `switch` case's body:
```swift
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."
```
<!--
- test: `enums`
```swifttest
-> switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
<- QR code: ABCDEFGHIJKLMNOP.
```
-->
If all of the associated values for an enumeration case
are extracted as constants, or if all are extracted as variables,
you can place a single `let` or `var` annotation before the case name, for brevity:
```swift
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."
```
<!--
- test: `enums`
```swifttest
-> switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
<- QR code: ABCDEFGHIJKLMNOP.
```
-->
When you're matching just one case of an enumeration ---
for example,
to extract its associated value ---
you can use an `if`-`case` statement
instead of writing a full switch statement.
Here's what it looks like:
```swift
if case .qrCode(let productCode) = productBarcode {
print("QR code: \(productCode).")
}
```
Just like in the switch statement earlier,
the `productBarcode` variable is matched against
the pattern `.qrCode(let productCode)` here.
And as in the switch case,
writing `let` extracts the associated value as a constant.
For more information about `if`-`case` statements,
see <doc:ControlFlow#Patterns>.
## Raw Values
The barcode example in <doc:Enumerations#Associated-Values>
shows how cases of an enumeration can declare that they store
associated values of different types.
As an alternative to associated values,
enumeration cases can come prepopulated with default values
(called *raw values*),
which are all of the same type.
Here's an example that stores raw ASCII values alongside named enumeration cases:
```swift
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
```
<!--
- test: `rawValues`
```swifttest
-> enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
```
-->
Here, the raw values for an enumeration called `ASCIIControlCharacter`
are defined to be of type `Character`,
and are set to some of the more common ASCII control characters.
`Character` values are described in <doc:StringsAndCharacters>.
Raw values can be
strings, characters, or any of the integer or floating-point number types.
Each raw value must be unique within its enumeration declaration.
Although you can use both raw values and associated values
to give an enumeration an additional value,
it's important to understand the difference between them.
You pick the raw value for an enumeration case
when you define that enumeration case in your code,
such as the three ASCII codes above.
The raw value for a particular enumeration case is always the same.
In contrast,
you pick associated values when you create a new constant or variable
using one of the enumeration's cases,
and you can pick a different value each time you do so.
### Implicitly Assigned Raw Values
When you're working with enumerations that store integer or string raw values,
you don't have to explicitly assign a raw value for each case.
When you don't, Swift automatically assigns the values for you.
For example, when integers are used for raw values,
the implicit value for each case is one more than the previous case.
If the first case doesn't have a value set, its value is `0`.
The enumeration below is a refinement of the earlier `Planet` enumeration,
with integer raw values to represent each planet's order from the sun:
```swift
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
```
<!--
- test: `rawValues`
```swifttest
-> enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
```
-->
In the example above,
`Planet.mercury` has an explicit raw value of `1`,
`Planet.venus` has an implicit raw value of `2`, and so on.
When strings are used for raw values,
the implicit value for each case is the text of that case's name.
The enumeration below is a refinement of the earlier `CompassPoint` enumeration,
with string raw values to represent each direction's name:
```swift
enum CompassPoint: String {
case north, south, east, west
}
```
<!--
- test: `rawValues`
```swifttest
-> enum CompassPoint: String {
case north, south, east, west
}
```
-->
In the example above,
`CompassPoint.south` has an implicit raw value of `"south"`, and so on.
You access the raw value of an enumeration case with its `rawValue` property:
```swift
let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"
```
<!--
- test: `rawValues`
```swifttest
-> let earthsOrder = Planet.earth.rawValue
/> earthsOrder is \(earthsOrder)
</ earthsOrder is 3
-> let sunsetDirection = CompassPoint.west.rawValue
/> sunsetDirection is \"\(sunsetDirection)\"
</ sunsetDirection is "west"
```
-->
### Initializing from a Raw Value
If you define an enumeration with a raw-value type,
the enumeration automatically receives an initializer
that takes a value of the raw value's type (as a parameter called `rawValue`)
and returns either an enumeration case or `nil`.
You can use this initializer to try to create a new instance of the enumeration.
This example identifies Uranus from its raw value of `7`:
```swift
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
```
<!--
- test: `rawValues`
```swifttest
-> let possiblePlanet = Planet(rawValue: 7)
>> print(type(of: possiblePlanet))
<< Optional<Planet>
>> assert(possiblePlanet == .uranus)
// possiblePlanet is of type Planet? and equals Planet.uranus
```
-->
Not all possible `Int` values will find a matching planet, however.
Because of this, the raw value initializer always returns an *optional* enumeration case.
In the example above, `possiblePlanet` is of type `Planet?`,
or “optional `Planet`.”
> Note: The raw value initializer is a failable initializer,
> because not every raw value will return an enumeration case.
> For more information, see <doc:Declarations#Failable-Initializers>.
If you try to find a planet with a position of `11`,
the optional `Planet` value returned by the raw value initializer will be `nil`:
```swift
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
} else {
print("There isn't a planet at position \(positionToFind)")
}
// Prints "There isn't a planet at position 11".
```
<!--
- test: `rawValues`
```swifttest
-> let positionToFind = 11
-> if let somePlanet = Planet(rawValue: positionToFind) {
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
} else {
print("There isn't a planet at position \(positionToFind)")
}
<- There isn't a planet at position 11
```
-->
This example uses optional binding to try to access a planet with a raw value of `11`.
The statement `if let somePlanet = Planet(rawValue: 11)` creates an optional `Planet`,
and sets `somePlanet` to the value of that optional `Planet` if it can be retrieved.
In this case, it isn't possible to retrieve a planet with a position of `11`,
and so the `else` branch is executed instead.
<!--
TODO: Switch around the order of this chapter so that all of the non-union stuff
is together, and the union bits (aka Associated Values) come last.
-->
## Recursive Enumerations
A *recursive enumeration* is an enumeration
that has another instance of the enumeration
as the associated value for one or more of the enumeration cases.
You indicate that an enumeration case is recursive
by writing `indirect` before it,
which tells the compiler to insert the necessary layer of indirection.
For example, here is an enumeration that stores simple arithmetic expressions:
```swift
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
```
<!--
- test: `recursive-enum-intro`
```swifttest
-> enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
```
-->
You can also write `indirect` before the beginning of the enumeration
to enable indirection for all of the enumeration's cases that have an associated value:
```swift
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
```
<!--
- test: `recursive-enum`
```swifttest
-> indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
```
-->
This enumeration can store three kinds of arithmetic expressions:
a plain number,
the addition of two expressions,
and the multiplication of two expressions.
The `addition` and `multiplication` cases have associated values
that are also arithmetic expressions ---
these associated values make it possible to nest expressions.
For example, the expression `(5 + 4) * 2`
has a number on the right-hand side of the multiplication
and another expression on the left-hand side of the multiplication.
Because the data is nested,
the enumeration used to store the data also needs to support nesting ---
this means the enumeration needs to be recursive.
The code below shows the `ArithmeticExpression` recursive enumeration
being created for `(5 + 4) * 2`:
```swift
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
```
<!--
- test: `recursive-enum`
```swifttest
-> let five = ArithmeticExpression.number(5)
-> let four = ArithmeticExpression.number(4)
-> let sum = ArithmeticExpression.addition(five, four)
-> let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
```
-->
A recursive function is a straightforward way
to work with data that has a recursive structure.
For example, here's a function that evaluates an arithmetic expression:
```swift
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(product))
// Prints "18".
```
<!--
- test: `recursive-enum`
```swifttest
-> func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
-> print(evaluate(product))
<- 18
```
-->
This function evaluates a plain number
by simply returning the associated value.
It evaluates an addition or multiplication
by evaluating the expression on the left-hand side,
evaluating the expression on the right-hand side,
and then adding them or multiplying them.
<!--
This source file is part of the Swift.org open source project
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
-->