Enumerations are a common design pattern in many programming languages. While you may be familiar with enumerations in C and Objective-C, Swift's implementation of enumerations is significantly more powerful and flexible. In this quick tip, you'll learn what's special about enumerations in Swift, how to use them in your projects, and what makes them so powerful.
1. What Is an Enumeration?
Enumerations aren't new and they're certainly not unique to Swift. However, if you're familiar with enumerations in C, then you're going to love Swift's powerful take on enumerations.
If enums or enumerations are new to you, then you may not be familiar with what they have to offer. In Swift, enumerations are first class types that define a list of possible values for that type.
An example might be the possible states of a network connection. The possible states could be:
- disconnected
- connecting
- connected
We could add a fourth state for the case the state is unknown. With this example in mind, let's see how to define and implement such an enumeration.
Basics
Like I said, enumerations are first class types in Swift. An enumeration definition looks very similar to a class or structure definition. In the example below, we define the ConnectionState
enumeration.
enum ConnectionState { }
The name of the enumeration is preceded by the enum
keyword and followed by a pair of curly braces. The ConnectionState
enumeration will define the possible states of a network connection. To define these states, we add member values or members to the enumeration's definition. The definition of a member value always starts with the case
keyword.
enum ConnectionState { case Unknown case Disconnected case Connecting case Connected }
In C or Objective-C, the above enumeration would look a bit different as illustrated in the example below. Each value of the enumeration corresponds with an integer, for example, ConnectionStateUnknown
equals 0
, ConnectionStateDisconnected
equals 1
, etc.
typedef enum : NSUInteger { ConnectionStateUnknown, ConnectionStateDisconnected, ConnectionStateConnecting, ConnectionStateConnected } ConnectionState;
This isn't true in Swift. The members of an enumeration don't automatically correspond with an integer value. The members of the ConnectionState
enumeration are values themselves and they are of type ConnectionState
. This makes working with enumerations safer and more explicit.
Raw Values
It is possible to explicitly specify the values of the members of an enumeration. In the following example, the members of the ConnectionState
enumeration have a raw value of type Int
. Each member is assigned a raw value, corresponding with an integer.
enum ConnectionState: Int { case Unknown = -1 case Disconnected = 0 case Connecting = 1 case Connected = 2 }
Note that we specify the type of the raw values in the enumeration's definition and that no two member values can have the same raw value. If we only specify a value for the Unknown
member, then Swift will automatically increment the value of the Unknown
member and assign unique values to the other members of the enumeration. To better illustrate this, the below example is identical to the previous definition of the ConnectionState
enumeration.
enum ConnectionState: Int { case Unknown = -1 case Disconnected case Connecting case Connected }
2. Working with Enumerations
Initialization
Using the ConnectionState
enumeration is similar to using any other type in Swift. In the next example, we declare a variable, connectionState
, and set its value to ConnectionState.Connecting
.
var connectionState = ConnectionState.Connecting
The value of connectionState
is ConnectionState.Connecting
and the variable is of type ConnectionState
.
Swift's type inference is very convenient when working with enumerations. Because we declared connectionState
as being of type ConnectionState
, we can now assign a new value by using the shorthand dot syntax for enumerations.
connectionState = .Connected
Control Flow
Using enumerations in an if
or switch
statement is straightforward. Remember that switch
statements need to be exhaustive. Add a default
case if necessary.
enum ConnectionState { case Unknown case Disconnected case Connecting case Connected } var connectionState = ConnectionState.Connecting connectionState = .Connected switch connectionState { case .Disconnected: println("Disconnected") case .Connecting: println("Connecting") case .Connected: println("Connected") default: println("Unknown State") }
The following example demonstrates how the ConnectionState
enum can be used. It also shows how to access the associated value of an enum member. The canConnect
function accepts a ConnectionState
instance and returns a Bool
.
func canConnect(connectionState: ConnectionState) -> Bool { var result = false switch connectionState { case .Connected(let port): if port == 3000 { result = true } default: result = false } return result } let state = ConnectionState.Connected(3000) if canConnect(state) { // ... }
The canConnect
function only returns true
if the ConnectionState
instance passed to the function is equal to .Connected
and its associated value is an Int
equal to 3000
. Note that the associated value of the Connected
member is available in the switch
statement as a constant named port
, which we can then use in the corresponding case
.
3. Associated Values
Another compelling feature of Swift enums are associated values. Each member of an enum can have an associated value. Associated values are very flexible. For example, associated values of different members of the same enum don't need to be of the same type. Take a look at the following example to better understand the concept of associated values.
enum ConnectionState { case Unknown case Disconnected case Connecting(Int, Double) case Connected(Int) }
The Unknown
and Disconnected
members don't have an associated value. TheConnecting
member has an associated value of type (Int, Double)
, specifying the port number and timeout interval of the connection. The Connected
member has an associated value of type Int
, specifying the port number.
It's important to understand that an associated value is linked to or associated with a member of the enumeration. The member's value remains unchanged. The next example illustrates how to create a ConnectionState
instance with an associated value.
var connectionState = ConnectionState.Connecting(3000, 30.0)
4. Methods and Value Types
Methods
Enumerations are pretty powerful in Swift. Enumerations can even define methods, such as an initializer to select a default member value if none was specified.
enum ConnectionState { case Unknown case Disconnected case Connecting(Int, Double) case Connected(Int) init () { self = .Unknown } } var connectionState = ConnectionState() // .Unknown
In this example, we initialize an instance of the ConnectionState
enumeration without explicitly specifying a value for it. In the initializer of the enumeration, however, we set the instance to Unknown
. The result is that the connectionState
variable is equal to ConnectionState.Unknown
.
Value Types
Like structures, enumerations are value types, which means that an enumeration is not passed by reference, like class instances, but by value. The following example illustrates this.
var connectionState1 = ConnectionState() var connectionState2 = connectionState1 connectionState1 = .Connected(1000) println(connectionState1) // .Connected(1000) println(connectionState2) // .Unknown
Even though we assign connectionState1
to connectionState2
, the values of connectionState1
and connectionState2
are different at the end of the example.
When connectionState1
is assigned to connectionState2
, Swift creates a copy of connectionState1
and assigns that to connectionState2
. In other words, connectionState1
and connectionState2
refer to two different ConnectionState
instances.
Conclusion
Enums in Swift are incredibly powerful compared to, for example, enums in C. One of the most powerful aspects of enumerations is that they are a first class types in Swift. Type safety is a key aspect of the Swift language and enumerations fit perfectly in that mindset.