Enums (short for enumerations) in Swift are a powerful way to define a group of related values in a type-safe way. Enums allow you to organize and work with sets of values that are semantically related. Swift enums go beyond the basic enumerations found in other languages, as they support associated values, computed properties, methods, and even conform to protocols.
In this article, we’ll explore enums in Swift—how to define them, use them with associated values, handle raw values, and extend their functionality with methods and properties. By the end, you’ll have a solid understanding of how to work with enums in your Swift projects.
What is an Enum?
An enum in Swift is a type that represents a group of related values. Each value in the enum is called a case, and it can be a standalone value or associated with more complex data. Enums are useful for modeling distinct states or conditions in your code, such as days of the week, error codes, or types of user actions.
Here’s the basic syntax for defining an enum:
enum CompassDirection {
case north
case south
case east
case west
}
In this example, CompassDirection
is an enum with four possible values: .north
, .south
, .east
, and .west
. You can use these cases to represent different directions.
Defining and Using Enums
Once you define an enum, you can create variables or constants of that enum type and assign them one of the defined cases.
Example 1: Using Enum Cases
enum CompassDirection {
case north
case south
case east
case west
}
var currentDirection = CompassDirection.north
currentDirection = .west // You can omit the enum name when the type is already known
if currentDirection == .west {
print("Heading west!")
}
// Output: Heading west!
In this example, currentDirection
is a variable of type CompassDirection
, and we assign it the value .west
.
Enum with Switch Statement
Enums work particularly well with switch statements. Since enums have a fixed set of possible values, Swift can help ensure that you handle all cases in the switch statement, making your code more reliable.
Example 2: Enum with Switch Statement
enum CompassDirection {
case north
case south
case east
case west
}
let direction = CompassDirection.east
switch direction {
case .north:
print("Going north")
case .south:
print("Going south")
case .east:
print("Going east")
case .west:
print("Going west")
}
// Output: Going east
In this example, we use a switch statement to handle each possible case of CompassDirection
. This ensures that all possible directions are accounted for.
Associated Values
One of the most powerful features of Swift enums is the ability to store associated values with each case. This allows you to store additional data alongside the enum case. Associated values are useful when each case needs to store different kinds of data.
Example 3: Enum with Associated Values
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("XYZ-12345")
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let checkDigit):
print("UPC: \(numberSystem)-\(manufacturer)-\(product)-\(checkDigit)")
case .qrCode(let code):
print("QR code: \(code)")
}
// Output: QR code: XYZ-12345
In this example, the Barcode
enum can represent either a UPC barcode or a QR code. Each case stores different associated values: the upc
case stores four integers, while the qrCode
case stores a string.
Raw Values
Enums can also have raw values, which are constant values associated with each case. Raw values are particularly useful when each case needs to map to a specific value, such as a string or an integer.
Example 4: Enum with Raw Values
enum Planet: Int {
case mercury = 1
case venus
case earth
case mars
}
let planet = Planet.earth
print("Earth is planet number \(planet.rawValue)")
// Output: Earth is planet number 3
In this example, the Planet
enum has raw values of type Int
. The case mercury
is explicitly assigned the value 1
, and the remaining cases are assigned values automatically, incrementing from 1
.
You can also initialize an enum from a raw value using the init?(rawValue:)
initializer.
Example 5: Initializing from a Raw Value
if let planet = Planet(rawValue: 2) {
print("The planet is \(planet)")
} else {
print("No planet found for this raw value.")
}
// Output: The planet is venus
In this example, the raw value 2
corresponds to the case venus
.
Methods in Enums
In Swift, enums can have methods, just like classes and structs. This allows you to define behavior inside the enum itself, making your code more modular and expressive.
Example 6: Enum with Methods
enum CompassDirection {
case north, south, east, west
func description() -> String {
switch self {
case .north:
return "You are heading north."
case .south:
return "You are heading south."
case .east:
return "You are heading east."
case .west:
return "You are heading west."
}
}
}
let direction = CompassDirection.west
print(direction.description()) // Output: You are heading west.
In this example, the description()
method provides a string description of the current direction. You can call this method on any case of the CompassDirection
enum.
Enum with Computed Properties
Enums can also have computed properties, allowing you to add custom logic that calculates a value based on the current case.
Example 7: Enum with Computed Properties
enum BeverageSize: Int {
case small = 8
case medium = 12
case large = 16
var description: String {
switch self {
case .small:
return "Small (8 oz)"
case .medium:
return "Medium (12 oz)"
case .large:
return "Large (16 oz)"
}
}
}
let size = BeverageSize.medium
print(size.description) // Output: Medium (12 oz)
In this example, the description
computed property returns a string based on the selected beverage size.
Enum with Recursive Associated Values
In some cases, enums need to refer to themselves in their associated values. This is known as recursive enums, and Swift supports this by marking the enum as indirect
.
Example 8: Recursive Enum
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let expression = ArithmeticExpression.addition(.number(5), .multiplication(.number(2), .number(3)))
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case .number(let value):
return value
case .addition(let left, let right):
return evaluate(left) + evaluate(right)
case .multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(expression)) // Output: 11
In this example, the ArithmeticExpression
enum is used to represent mathematical expressions. The indirect
keyword allows the enum to recursively refer to itself.
Enum Conformance to Protocols
Enums can conform to protocols just like structs and classes, which allows them to adopt shared behavior.
Example 9: Enum Conforming to a Protocol
protocol Describable {
func describe() -> String
}
enum Weather: Describable {
case sunny
case cloudy
case rainy
func describe() -> String {
switch self {
case .sunny:
return "It's sunny today."
case .cloudy:
return "It's cloudy today."
case .rainy:
return "It's rainy today."
}
}
}
let currentWeather = Weather.rainy
print(currentWeather.describe()) // Output: It's rainy today.
In this example, the Weather
enum conforms to the Describable
protocol by implementing the describe()
method.
Real-World Example: Managing App States with Enums
Enums are often used to manage distinct states in an app. For example, you might use an enum to represent the different states of a network request.
enum NetworkState {
case loading
case success(data: String)
case failure(error: String)
}
let state = NetworkState.success(data: "User profile data")
switch state {
case .loading:
print("Loading...")
case .success(let data):
print("Data received: \(data)")
case .failure(let error):
print("Error occurred: \(error)")
}
// Output: Data received: User profile data
In this real-world example, the NetworkState
enum represents three possible states: loading
, success
, and failure
. The success
and failure
cases have associated values to store data and error messages, respectively.
Best Practices for Using Enums
- Use enums for type safety: Enums help ensure that you work with well-defined, distinct values, which reduces the risk of invalid states in your code.
- Leverage associated values: Use associated values to add additional context or data to enum cases, making your enums more expressive and versatile.
- Switch exhaustively: Swift forces you to handle all enum cases in switch statements. This helps prevent bugs and ensures that your code can handle every possible state.
- Conform to protocols: Enums can conform to protocols, allowing you to standardize behavior across different types.
- Use raw values where appropriate: If your enum cases correspond to constant values (like integers or strings), consider using raw values to map each case to a specific value.
Conclusion
Enums are a fundamental part of Swift, offering a powerful way to define a common type for a group of related values. They allow you to model state, handle complex data with associated values, and even define behavior using methods and computed properties.
In this article, we covered:
- Basic enum syntax and how to define and use enums.
- Associated values for storing additional data with enum cases.
- Raw values for mapping enum cases to constant values.
- Adding methods and computed properties to enums.
- Recursive enums for more complex data structures.
- Protocol conformance to extend the functionality of enums.
In the next article, we’ll explore structs in Swift—how to use them to define custom data types, and how they compare to classes and enums.
Happy coding!