AppVenture has been updated

All articles have been rewritten and improved. You will be forwarded to the updated article.

Click here to go there directly.

Sat, 17 Oct 2015 #

Advanced and Practical Enum usage in Swift

When and how to use enums in Swift? This is a detailed practical overview of all the possibilities enums can offer you.

Similarly to the switch statement, enum's in Swift may at first glance look like a slightly improved variant of the well known C enum statement. I.e. a type that allows you to define that something is "one of something more general". However, upon close introspection, the particular design decisions behind Swift's enums allow it to be used in a much wider range of practical scenarios than plain C enums. In particular, they're great tools to clearly manifest the intentions of your code.

In this post, we'll first look at the syntax and possibilities of using enum, and will then use them in a variety of (hopefully) practical, real world scenarios to give a better idea of how and when to use them. We'll also look a bit at how enums are being used in the Swift Standard library.

Before we dive in, here's a definition of what enums can be. We'll revisit this definition later on:

"Enums declare types with finite sets of possible states and accompanying values. With nesting, methods, associated values, and pattern matching, however, enums can define any hierarchically organized data."

1 Diving In

A short overview of how to define and use enums.

1.1 Defining Basic Enums

We're working on a game, and the player can move in four directions. So our player movement is restricted. Obviously, we can use an enum for that.

enum Movement {
case Left
case Right
case Top
case Bottom
}

You can then use various pattern matching constructs to retrieve the value of a Movement, or act upon a specific case:

let aMovement = Movement.Left

switch aMovement {
case .Left: print("left")
default: ()
}

if case .Left = aMovement { print("left") }

if aMovement == .Left { print("left") }

Note that you don't have to specify the actual name of the enum (i.e. case Movement.Left: print("Left") in this case. The type checker figures that out automatically. This is extremely helpful for some of those convoluted UIKit or AppKit enums.

1.2 Enum Values

Of course, you may want to have a value assigned to each enum case. This is useful if the enum itself indeed relates to something which can be expressed in a different type. C allows you to assign numbers to enum cases. Swift gives you much more flexibility here:

// Mapping to Integer
enum Movement: Int {
    case Left = 0
    case Right = 1
    case Top = 2
    case Bottom = 3
}

// You can also map to strings
enum House: String {
    case Baratheon = "Ours is the Fury"
    case Greyjoy = "We Do Not Sow"
    case Martell = "Unbowed, Unbent, Unbroken"
    case Stark = "Winter is Coming"
    case Tully = "Family, Duty, Honor"
    case Tyrell = "Growing Strong"
}

// Or to floating point (also note the fancy unicode in enum cases)
enum Constants: Double {
    case π = 3.14159
    case e = 2.71828
    case φ = 1.61803398874
    case λ = 1.30357
}

For String and Int types, you can even omit the values and the Swift compiler will do the right thing:

// Mercury = 1, Venus = 2, ... Neptune = 8
enum Planet: Int {
    case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}

// North = "North", ... West = "West"
enum CompassPoint: String {
    case North, South, East, West
}

Swift supports the following types for the value of an enum:

  • Integer
  • Floating Point
  • String
  • Boolean

So you won't be able1 to use, say, a CGPoint as the value of your enum.

If you want to access the values, you can do so with the rawValue property:

let bestHouse = House.Stark
print(bestHouse.rawValue)
// prints "Winter is coming"

However, there may also be a situation where you want to construct an enum case from an existing raw value. In that case, there's a special initializer for enums:

enum Movement: Int {
    case Left = 0
    case Right = 1
    case Top = 2
    case Bottom = 3
}
// creates a movement.Right case, as the raw value for that is 1
let rightMovement = Movement(rawValue: 1)

If you use the rawValue initializer, keep in mind that it is a failable initializer, i.e. you get back an Optional, as the value you're using may not map to any case at all, say if you were to write Movement(rawValue: 42).

This is a very useful feature in case you want to encode low level C binary representations into something much more readable. As an example, have a look as this encoding of the VNode Flags for the BSD kqeue library:

enum VNodeFlags : UInt32 {
    case Delete = 0x00000001
    case Write = 0x00000002
    case Extended = 0x00000004
    case Attrib = 0x00000008
    case Link = 0x00000010
    case Rename = 0x00000020
    case Revoke = 0x00000040
    case None = 0x00000080
}

This allows you to use the much nicer looking Delete or Write cases, and later on hand the raw value into the C function only when it is really needed.

1.3 Nesting Enums

If you have specific sub type requirements, you can also logically nest enums in an enum. This allows you to contain specific information on your enum case within the actual enum. Imagine a character in an RPG. Each character can have a weapon, all characters have access to the same set of weapons. All other instances in the game do not have access to those weapons (they're trolls, they just have clubs).

enum Character {
  enum Weapon {
    case Bow
    case Sword
    case Lance
    case Dagger
  }
  enum Helmet {
    case Wooden
    case Iron
    case Diamond
  }
  case Thief
  case Warrior
  case Knight
}

Now you have a hierachical system to describe the various items that your character has access to.

let character = Character.Thief
let weapon = Character.Weapon.Bow
let helmet = Character.Helmet.Iron

1.4 Containing Enums

In a similar vein, you can also embed enums in structs or classes. Continuing with our previous example:

struct Character {
   enum CharacterType {
    case Thief
    case Warrior
    case Knight
  }
  enum Weapon {
    case Bow
    case Sword
    case Lance
    case Dagger
  }
  let type: CharacterType
  let weapon: Weapon
}

let warrior = Character(type: .Warrior, weapon: .Sword)

This, again, helps in keeping related information together.

1.5 Associated Values

Associated values are a fantastic way of attaching additional information to an enum case. Say you're writing a trading engine, and there're two different possible trade types. Buy and Sell. Each of them would be for a specific stock and amount:

1.5.1 Simple Example

enum Trade {
    case Buy
    case Sell
}
func trade(tradeType: Trade, stock: String, amount: Int) {}

However, the stock and amount clearly belong to the trade in question, having them as separate parameters feels unclean. You could embed it into a struct, but associated values allow for a much cleaner solution:

enum Trade {
    case Buy(stock: String, amount: Int)
    case Sell(stock: String, amount: Int)
}
func trade(type: Trade) {}

1.5.2 Pattern Matching

If you want to access this information, again, pattern matching comes to the rescue:


let trade = Trade.Buy(stock: "AAPL", amount: 500)
if case let Trade.Buy(stock, amount) = trade {
    print("buy \(amount) of \(stock)")
}

1.5.3 Labels

Associated values do not require labels:

enum Trade {
   case Buy(String, Int)
   case Sell(String, Int)
}

If you add them, though, you'll have to type them out when creating your enum cases.

1.5.4 Tuples as Arguments

What's more, the Swift internal associated information is just a Tuple, so you can do things like this:


let tp = (stock: "TSLA", amount: 100)
let trade = Trade.Sell(tp)

if case let Trade.Sell(stock, amount) = trade {
    print("buy \(amount) of \(stock)")
}
// Prints: "buy 100 of TSLA"

This syntax allows you to take Tuples as a simple data structure and later on automatically elevate them into a higher type like an enum case. Imagine an app where a user can configure a Desktop that he wants to order:

typealias Config = (RAM: Int, CPU: String, GPU: String)

// Each of these takes a config and returns an updated config
func selectRAM(_ config: Config) -> Config {return (RAM: 32, CPU: config.CPU, GPU: config.GPU)}
func selectCPU(_ config: Config) -> Config {return (RAM: config.RAM, CPU: "3.2GHZ", GPU: config.GPU)}
func selectGPU(_ config: Config) -> Config {return (RAM: config.RAM, CPU: config.CPU, GPU: "NVidia")}

enum Desktop {
   case Cube(Config)
   case Tower(Config)
   case Rack(Config)
}

let aTower = Desktop.Tower(selectGPU(selectCPU(selectRAM((0, "", "") as Config))))

Each step of the configuration updates a tuple which is handed in to the enum at the end. This works even better if we take a hint from functional programming apply 2:


infix operator <^> { associativity left }

func <^>(a: Config, f: (Config) -> Config) -> Config { 
    return f(a)
}

Finally, we can thread through the different configuration steps. This is particularly helpful if you have many of those steps.


let config = (0, "", "") <^> selectRAM  <^> selectCPU <^> selectGPU
let aCube = Desktop.Cube(config)

1.5.5 Use Case Examples

Associated Values can be used in a variety of ways. As code can tell more than a thousand words, what follows is a list of short examples in no particular order.

// Cases can have different values
enum UserAction {
  case OpenURL(url: NSURL)
  case SwitchProcess(processId: UInt32)
  case Restart(time: NSDate?, intoCommandLine: Bool)
}

// Or imagine you're implementing a powerful text editor that allows you to have
// multiple selections, like Sublime Text here:
// https://www.youtube.com/watch?v=i2SVJa2EGIw
enum Selection {
  case None
  case Single(Range<Int>)
  case Multiple([Range<Int>])
}

// Or mapping different types of identifier codes
enum Barcode {
    case UPCA(numberSystem: Int, manufacturer: Int, product: Int, check: Int)
    case QRCode(productCode: String)
}

// Or, imagine you're wrapping a C library, like the Kqeue BSD/Darwin notification
// system: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
enum KqueueEvent {
    case UserEvent(identifier: UInt, fflags: [UInt32], data: Int)
    case ReadFD(fd: UInt, data: Int)
    case WriteFD(fd: UInt, data: Int)
    case VnodeFD(fd: UInt, fflags: [UInt32], data: Int)
    case ErrorEvent(code: UInt, message: String)
}

// Finally, all user-wearable items in an RPG could be mapped with one
// enum, that encodes for each item the additional armor and weight
// Now, adding a new material like 'Diamond' is just one line of code and we'll have the option to add several new Diamond-Crafted wearables.
enum Wearable {
    enum Weight: Int {
	case Light = 1
	case Mid = 4
	case Heavy = 10
    }
    enum Armor: Int {
	case Light = 2
	case Strong = 8
	case Heavy = 20
    }
    case Helmet(weight: Weight, armor: Armor)
    case Breastplate(weight: Weight, armor: Armor)
    case Shield(weight: Weight, armor: Armor)
}
let woodenHelmet = Wearable.Helmet(weight: .Light, armor: .Light)

1.6 Methods and Properties

You can also define methods on an enum like so:

enum Wearable {
    enum Weight: Int {
	case Light = 1
    }
    enum Armor: Int {
	case Light = 2
    }
    case Helmet(weight: Weight, armor: Armor)
    func attributes() -> (weight: Int, armor: Int) {
       switch self {
	 case .Helmet(let w, let a): return (weight: w.rawValue * 2, armor: a.rawValue * 4)
       }
    }
}
let woodenHelmetProps = Wearable.Helmet(weight: .Light, armor: .Light).attributes()
print (woodenHelmetProps)
// prints "(2, 8)"

Methods on enums exist for every enum case. So if you want to have specific code for specific cases, you need a branch or a switch to determine the correct code path.

enum Device { 
    case iPad, iPhone, AppleTV, AppleWatch 
    func introduced() -> String {
       switch self {
	 case .AppleTV: return "\(self) was introduced 2006"
	 case .iPhone: return "\(self) was introduced 2007"
	 case .iPad: return "\(self) was introduced 2010"
	 case .AppleWatch: return "\(self) was introduced 2014"
       }
    }
}
print (Device.iPhone.introduced())
// prints: "iPhone was introduced 2007"

1.6.1 Properties

Even though you can't add actual stored properties to an enum, you can still create computed properties. Their contents, of course, can be based on the enum value or enum associated value.

enum Device {
  case iPad, iPhone
  var year: Int {
    switch self {
	case .iPhone: return 2007
	case .iPad: return 2010
     }
  }
}

1.6.2 Static Methods

You can also have static methods on enums, i.e. in order to create an enum from a non-value type. In this example we want to get the proper Apple Device for the wrong name that's sometimes used by people.

enum Device { 
    case AppleWatch 
    static func fromSlang(term: String) -> Device? {
      if term == "iWatch" {
	  return .AppleWatch
      }
      return nil
    }
}
print (Device.fromSlang(term:"iWatch")!)

1.6.3 Mutating Methods

Methods can be declared mutating. They're then allowed to change the case of the underlying self parameter 3:

enum TriStateSwitch {
    case Off, Low, High
    mutating func next() {
	switch self {
	case .Off:
	    self = .Low
	case .Low:
	    self = .High
	case High:
	    self = .Off
	}
    }
}
var ovenLight = TriStateSwitch.Low
ovenLight.next()
// ovenLight is now equal to .High
ovenLight.next()
// ovenLight is now equal to .Off

1.7 To Recap

We've finished our overview of the basic use cases of Swift's enum syntax. Before we head into the advanced usage, lets have another look at the explanation we gave at the beginning and see if it became clearer now.

Enums declare types with finite sets of possible states and accompanying values. With nesting, methods, associated values, and pattern matching, however, enums can define any hierarchically organized data.

The definition is a lot clearer now. Indeed, if we add associated values and nesting, an enum case is like a closed, simplified struct. The advantage over structs being the ability to encode categorization and hierachy:

// Struct Example
struct Point { let x: Int, y: Int }
struct Rect { let x: Int, y: Int, width: Int, height: Int }

// Enum Example
enum GeometricEntity {
   case Point(x: Int, y: Int)
   case Rect(x: Int, y: Int, width: Int, height: Int)
}

The addition of methods and static methods allow us to attach functionality to an enum without having to resort to free functions 4

// C-Like example
enum Trade {
   case Buy
   case Sell
}
func order(trade: Trade)

// Swift Enum example
enum Trade {
   case Buy
   case Sell
   func order() {}
}

2 Advanced Enum Usage

2.1 Protocols

I already mentioned the similarity between the structs and enums. In addition to the ability to add methods, Swift also allows you to use Protocols and Protocol Extensions with enums.

Swift protocols define an interface or type that other structures can conform to. In this case our enum can conform to it. For a start, let's take a protocol from the Swift standard library.

CustomStringConvertible is a type with a customized textual representation suitable for printing purposes:

protocol CustomStringConvertible {
  var description: String { get }
}

It has only one requirement, namely a getter for a string. We can implement this on an enum quite easily:

enum Trade: CustomStringConvertible {
   case Buy, Sell
   var description: String {
       switch self {
	   case .Buy: return "We're buying something"
	   case .Sell: return "We're selling something"
       }
   }
}

let action = Trade.Buy.description
print("this action is \(action)")
// prints: this action is We're buying something

Some protocol implementations may need internal state handling to cope with the requirements. Imagine a protocol that manages a bank account:

protocol AccountCompatible {
  var remainingFunds: Int { get }
  mutating func addFunds(amount: Int) throws
  mutating func removeFunds(amount: Int) throws
}

You could easily fulfill this protocol with a struct, but in the context of your application, an enum is the more sensible approach. However, you can't add properties like var remainingFunds: Int to an enum, so how would you model that? The answer is actually easy, you can use associated values for this:

enum Account {
  case Empty
  case Funds(remaining: Int)

  enum Error: Swift.Error {
    case Overdraft(amount: Int)
  }

  var remainingFunds: Int {
    switch self {
    case .Empty: return 0
    case .Funds(let remaining): return remaining
    }
  }
}

To keep things clean, we can then define the required protocol functions in a protocol extension on the enum:


extension Account: AccountCompatible {

  mutating func addFunds(amount: Int) throws {
    var newAmount = amount
    if case let .Funds(remaining) = self {
      newAmount += remaining
    }
    if newAmount < 0 {
      throw Error.Overdraft(amount: -newAmount)
    } else if newAmount == 0 {
      self = .Empty
    } else {
      self = .Funds(remaining: newAmount)
    }
  }

  mutating func removeFunds(amount: Int) throws {
    try self.addFunds(amount * -1)
  }

}

var account = Account.Funds(remaining: 20)
print("add: ", try? account.addFunds(amount:10))
print ("remove 1: ", try? account.removeFunds(amount:15))
print ("remove 2: ", try? account.removeFunds(amount:55))
// prints:
// : add:  Optional(())
// : remove 1:  Optional(())
// : remove 2:  nil

As you can see, we implemented all the protocol requirements by storing our values within our enum cases. A very nifty side effect of this is, that now you can test for an empty account with a simple pattern match all over your code base. You don't have to see whether the remainingFunds are zero.

We're also nesting an ErrorType compatible enum in the Account enum so that we can use Swift 2.0's new error handling. This is explained in more detail in the Practical Use Cases section.

2.2 Extensions

As we just saw, enums can also be extended. The most apparent use case for this is keeping enum cases and methods separate, so that a reader of your code can easily digest the enum and after that move on to the methods:

enum Entities {
    case Soldier(x: Int, y: Int)
    case Tank(x: Int, y: Int)
    case Player(x: Int, y: Int)
}

Now, we can extend this enum with methods:

extension Entities {
   mutating func move(dist: CGVector) {}
   mutating func attack() {}
}

You can also write extensions to add support for a specific protocol:

extension Entities: CustomStringConvertible {
  var description: String {
    switch self {
       case let .Soldier(x, y): return "\(x), \(y)"
       case let .Tank(x, y): return "\(x), \(y)"
       case let .Player(x, y): return "\(x), \(y)"
    }
  }
}

2.3 Generic Enums

Enums can also be defined over generic parameters. You'd use them to adapt the associated values of an enum. The simplest example comes straight from the Swift standard library, namely the Optional type. You probably mostly use it with optional chaining (?), if let, guard let, or switch, but syntactically you can also use Optionals like so:

let aValue = Optional<Int>.some(5)
let noValue = Optional<Int>.none
if noValue == Optional.none { print("No value") }

This is the direct usage of an Optional without any of the syntactic sugar that Swift adds in order to make your life a tremendous amount easier. If you look at the code above, you can probably guess that internally the Optional is defined as follows 5:

// Simplified implementation of Swift's Optional
enum MyOptional<T> {
  case Some(T)
  case None
}

What's special here is, that the enum's associated values take the type of the generic parameter T, so that optionals can be built for any kind you wish to return.

Enums can have multiple generic parameters. Take the well-known Either type which is not part of Swift's standard library but implemented in many open source libraries as well as prevalent in other functional programming languages like Haskell or F#. The idea is that instead of just returning a value or no value (née Optional) you'd return either the successful value or something else (probably an error value).

// The well-known either type is, of course, an enum that allows you to return either
// value one (say, a successful value) or value two (say an error) from a function
enum Either<T1, T2> {
  case Left(T1)
  case Right(T2)
}

Finally, all the type constraints that work on classes and structs in Swift also work on enums.

// Totally nonsensical example. A bag that is either full (has an array with contents)
// or empty.
enum Bag<T: Sequence> where T.Iterator.Element==Equatable {
    case Empty
    case Full(contents: T)
}

2.4 Recursive / Indirect Types

Indirect types are a new addition that came with Swift 2.0. They allow you to define enums where the associated value of a case is the very same enum again. As an example, consider that you want to define a file system representations with files and folders containing files. If File and Folder were enum cases, then the Folder case would need to have an array of File cases as it's associated value. Since this is a recursive operation, the compiler has to make special preparations for it. Quoting from the Swift documentation:

Enums and cases can be marked indirect, which causes the associated value for the enum to be stored indirectly, allowing for recursive data structures to be defined.

So to implement our FileNode enum, we'd have to write it like this:

enum FileNode {
  case File(name: String)
  indirect case Folder(name: String, files: [FileNode])
}

The indirect keyword tells the compiler to handle this enum case indirectly. You can also add the keyword for the whole enum. As an example imagine mapping a binary tree:

indirect enum Tree<Element: Comparable> {
    case Empty
    case Node(Tree<Element>,Element,Tree<Element>)
}

This is a very powerful feature that allows you to map complex relationships in a very clean way with an enum.

2.5 Using Custom Data Types as Enum Values

If we neglect associated values, then the value of an enum can only be an Integer, Floating Point, String, or Boolean. If you need to support something else, you can do so by implementing the StringLiteralConvertible protocol which allows the type in question to be serialized to and from String.

As an example, imagine you'd like to store the different screen sizes of iOS devices in an enum:

enum Devices: CGSize {
   case iPhone3GS = CGSize(width: 320, height: 480)
   case iPhone5 = CGSize(width: 320, height: 568)
   case iPhone6 = CGSize(width: 375, height: 667)
   case iPhone6Plus = CGSize(width: 414, height: 736)
}

However, this doesn't compile. CGSize is not a literal and can't be used as an enum value. Instead, what you need to do is add a type extension for the StringLiteralConvertible protocol. The protocol requires us to implement three initializers each of them is being called with a String, and we have to convert this string into our receiver type (CGSize)

extension CGSize: ExpressibleByStringLiteral {
    public init(stringLiteral value: String) {
	let size = CGSizeFromString(value)
	self.init(width: size.width, height: size.height)
    }

    public init(extendedGraphemeClusterLiteral value: String) {
	let size = CGSizeFromString(value)
	self.init(width: size.width, height: size.height)
    }

    public init(unicodeScalarLiteral value: String) {
	let size = CGSizeFromString(value)
	self.init(width: size.width, height: size.height)
    }
}

Now, we can write our enum, with one downside though: The initial values have to be written as a String, since that's what the enum will use (remember, we complied with StringLiteralConvertible, so that the String can be converted to our CGSize type.

enum Devices: CGSize {
   case iPhone3GS = "{320, 480}"
   case iPhone5 = "{320, 568}"
   case iPhone6 = "{375, 667}"
   case iPhone6Plus = "{414, 736}"
}

This, finally, allows us to use our CGSize enum. Keep in mind that in order to get the actual CGSize value, we have to access the rawvalue of the enum.


let a = Devices.iPhone5
let b = a.rawValue
print("the phone size string is \(a), width is \(b.width), height is \(b.height)")
// prints : the phone size string is iPhone5, width is 320.0, height is 568.0

The String serialization requirement makes it a bit difficult to use any kind of type, but for some specific use cases, this can work well (such as NSColor / UIColor). However, you can also use this with your custom types obviously.

2.6 Comparing Enums with associated values

Enums, by nature, are easily comparable by equality. A simple enum T { case a, b} implementation supports the proper equality tests T.a == T.a, T.b != T.a.

If you add associated values though, Swift cannot correctly infer the equality of two enums, and you have to implement the == operator yourself. This is simple though:

enum Trade {
    case Buy(stock: String, amount: Int)
    case Sell(stock: String, amount: Int)
}
func ==(lhs: Trade, rhs: Trade) -> Bool {
   switch (lhs, rhs) {
     case let (.Buy(stock1, amount1), .Buy(stock2, amount2))
	   where stock1 == stock2 && amount1 == amount2:
	   return true
     case let (.Sell(stock1, amount1), .Sell(stock2, amount2))
	   where stock1 == stock2 && amount1 == amount2:
	   return true
     default: return false
   }
}

As you can see, we're comparing the two possible enum cases via a switch, and only if the cases match (i.e. .Buy & .Buy) will we compare the actual associated values.

2.7 Custom Initializers

In the context of static methods on enums, we already mentioned that they can be used as a way to conveniently create an enum from different data. The example we had was for returning the proper Apple device for the wrong worded version that the press sometimes uses:

enum Device { 
    case AppleWatch 
    static func fromSlang(term: String) -> Device? {
      if term == "iWatch" {
	  return .AppleWatch
      }
      return nil
    }
}

Instead of using a static method for this, we can also use a custom initializer. The main difference compared to a Swift struct or class is that within an enum initializer, you need to set the implicit self property to the correct case.

enum Device { 
    case AppleWatch 
    init?(term: String) {
      if term == "iWatch" {
	  self = .AppleWatch
      } else {
	  return nil
      }
    }
}

In the above example, we used a failable initializer. However, normal initializers work just as well:

enum NumberCategory {
   case Small
   case Medium
   case Big
   case Huge
   init(number n: Int) {
	if n < 10000 { self = .Small }
	else if n < 1000000 { self = .Medium }
	else if n < 100000000 { self = .Big }
	else { self = .Huge }
   }
}
let aNumber = NumberCategory(number: 100)
print(aNumber)
// prints: "Small"

2.8 Iterating over Enum Cases

One particularly often asked question with regards to enums is how to iterate over all cases. Sadly, enums do not conform to the SequenceType protocol, so there is no official way to do this. Depending on the type of enum that you have, it might be easier or more difficult to implement a way of iterating over all cases. There's a very good overview in this StackOverflow thread. Also, there's so much variation in the replies that it wouldn't to listing only some of the examples here. On the other hand, listing all the examples would be too much.

2.9 Objective-C support

Integer-based enums such as enum Bit: Int { case Zero = 0; case One = 1} can be bridged to Objective-c via the @objc flag. However once you venture away from integers (say String) or start using associated values you can't use enums from within Objective-C.

There's a hidden protocol called _ObjectiveCBridgeable which apparently allows defining the proper methods so that Swift can convert things back and forth from Objective-C, but I suppose there's a reason why it is hidden. Nevertheless, theoretically it should allow you to add support for bridging an enum (including associated values) to Objective-C.

You don't have to do it that way though. Add two methods to your enum, define a type replacement on the @objc side, and you can move enums back and forth just fine, without having to conform to private protocols:

enum Trade {
    case Buy(stock: String, amount: Int)
    case Sell(stock: String, amount: Int)
}

// This type could also exist in Objective-C code.
@objc class OTrade: NSObject {
    var type: Int
    var stock: String
    var amount: Int
    init(type: Int, stock: String, amount: Int) {
	self.type = type
	self.stock = stock
	self.amount = amount
    }
}

extension Trade  {

    func toObjc() -> OTrade {
	switch self {
	case let .Buy(stock, amount):
	    return OTrade(type: 0, stock: stock, amount: amount)
	case let .Sell(stock, amount):
	    return OTrade(type: 1, stock: stock, amount: amount)
	}
    }

    static func fromObjc(source: OTrade) -> Trade? {
	switch (source.type) {
	case 0: return Trade.Buy(stock: source.stock, amount: source.amount)
	case 1: return Trade.Sell(stock: source.stock, amount: source.amount)
	default: return nil
	}
    }
}

This still has the downside that you need to mirror your enum via an NSObject based type on the Objective-C side (or you could just go and use an NSDictionary), but if you ever end up in a situation where you need to access an enum with associated values from Objective-C, this is a way to do it.

2.10 Enum Internals

Erica Sadun wrote a great blog post explaining the internals of enums when you look at the bits and bytes behind the glossy syntax. This is something you should never do in production code, but still interesting to know. We'll only mention one of her findings here, but go and read her original article for more.

Enums are typically one byte long. […] If you want to get very very silly, you can build an enumeration with hundreds of cases, in which case the enum takes up 2 or more bytes depending on the minimum bit count needed.

3 Enums in the Swift Standard Library

Before we go on and explore various use cases for enums in your projects, it might be tempting to see some of the enums being used in the Swift standard library, so let's have a look.

Bit The Bit enum can have two possible values, One, and Zero. It is used as the Index type for CollectionOfOne<T>.

FloatingPointClassification This enum defines the set of possible IEEE 754 "classes", like NegativeInfinity, PositiveZero, or SignalingNaN.

Mirror.AncestorRepresentation, and Mirror.DisplayStyle These two are used in the context of the Swift Reflection API.

Optional Not much to say here

Process The Process enum contains the command line arguments of the current process (Process.argc, Process.arguments). This is a particularly interesting enum as it used to be a struct in Swift 1.0.

4 Practical Use Cases

We've already seen a couple of useful enums in the previous feature descriptions. Examples would be Optional, Either, FileNode, or the binary tree. However, there're many more scenarios where using an enum wins over a struct or class. Usually, if your problem domain can be divided into a finite set of distinctive categories, an enum may be the right choice. Even only two cases are a perfectly valid scenario for an enum, as the Optional and Either types show.

Here, then, are some more examples of practical enum usage to fuel your creativity.

Advanced and Practical Enum usage in Swift4.1 Error Handling

One of the prime examples of Enum usage in Swift is, of course, the new error handling in Swift 2.0. Your throwing function can throw anything which conforms to the empty ErrorType protocol. As the Swift documentation succinctly observes:

Swift enumerations are particularly well suited to modeling a group of related error conditions, with associated values allowing for additional information about the nature of an error to be communicated.

As an example, have a look at the popular JSON Decoding library Argo. When their JSON Decoding fails, it can fail due to two primary reasons.

  1. The JSON Data lacks a key which the end model requires (say your model has a property username and somehow the JSON lacks that)
  2. There's a type mismatch. Say instead of a String the username property in the JSON contains an NSNull 6.

In addition to that, Argo also includes a custom error for anything not fitting in these two categories above. Their ErrorType enum looks like this:

enum DecodeError: Error {
  case TypeMismatch(expected: String, actual: String)
  case MissingKey(String)
  case Custom(String)
}

All cases have associated values that contain additional information about the error in question.

A more general ErrorType for complete HTTP / REST API handling could look like this:

enum APIError : Error {
    // Can't connect to the server (maybe offline?)
    case ConnectionError(error: NSError)
    // The server responded with a non 200 status code
    case ServerError(statusCode: Int, error: NSError)
    // We got no data (0 bytes) back from the server
    case NoDataError
    // The server response can't be converted from JSON to a Dictionary
    case JSONSerializationError(error: Error)
    // The Argo decoding Failed
    case JSONMappingError(converstionError: DecodeError)
}

This ErrorType implements the complete REST Stack up to the point where your app would get the completely decoded native struct or class object.

If you look closely, you'll see that within the JSONMappingError, we're wrapping the Argo DecodeError into our APIError type as we're still using Argo for the actual JSON decoding.

More information on ErrorType and more enum examples in this context can be found in the official documentation here.

4.2 Observer Pattern

There're various ways of modelling observation in Swift. If you include @objc compatibility, you can use NSNotificationCenter or KVO. Even if not, the didSet syntax makes it easy to implement simple observation. Enums can be used here in order to make the type of change that happens to the observed object clearer. Imagine collection observation. If we think about it, we only have a couple of possible cases: One or more items are inserted, one or more items are deleted, one or more items are updated. This sounds like a job for an enum:

enum Change {
     case Insertion(items: [Item])
     case Deletion(items: [Item])
     case Update(items: [Item])
}

Then, the observing object can receive the concrete information of what happened in a very clean way. This could easily be extended by adding oldValue and newValue, too.

4.3 Status Codes

If you're working with an outside system which uses status codes (or error codes) to convey information, like HTTP Status Codes, enums are obviously a great way to encode the information. 7

enum HttpError: String {
  case Code400 = "Bad Request"
  case Code401 = "Unauthorized"
  case Code402 = "Payment Required"
  case Code403 = "Forbidden"
  case Code404 = "Not Found"
}

4.4 Map Result Types

Enums are also frequently used to map the result of JSON parsing into the Swift type system. Here's a short example of this:

enum JSON {
    case JSONString(Swift.String)
    case JSONNumber(Double)
    case JSONObject([String : JSONValue])
    case JSONArray([JSONValue])
    case JSONBool(Bool)
    case JSONNull
}

Similarly, if you're parsing something else, you may use the very same structure to convert your parsing results into Swift types. This also makes perfect sense to only do it during the parsing / processing step and then taking the JSON enum representation and converting it into one of your application's internal class or struct types.

4.5 UIKit Identifiers

Enums can be used to map reuse identifiers or storyboard identifiers from stringly typed information to something the type checker can understand. Imagine a UITableView with different prototype cells:

enum CellType: String {
    case ButtonValueCell = "ButtonValueCell"
    case UnitEditCell = "UnitEditCell"
    case LabelCell = "LabelCell"
    case ResultLabelCell = "ResultLabelCell"
}

4.6 Units

Units and unit conversion are another nice use case for enums. You can map the units and their respective values and then add methods to do automatic conversions. Here's an oversimplified example.

enum Liquid: Float {
  case ml = 1.0
  case l = 1000.0
  func convert(amount: Float, to: Liquid) -> Float {
      if self.rawValue < to.rawValue {
	 return (self.rawValue / to.rawValue) * amount
      } else {
	 return (self.rawValue * to.rawValue) * amount
      }
  }
}
// Convert liters to milliliters
print (Liquid.l.convert(amount: 5, to: Liquid.ml))

Another example of this would be Currency conversion. Also, mathematical symbols (such as degrees vs radians) can benefit from this.

4.7 Games

Enums are a great use case for games, where many entities on screen belong to a specific family of items (enemies, obstacles, textures, …). In comparison to native iOS or Mac apps, games oftentimes are a tabula rasa. Meaning you invent a new world with new relationships and new kinds of objects, whereas on iOS or OSX you're using a well-defined world of UIButtons, UITableViews, UITableViewCells or NSStackView.

What's more, since Enums can conform to protocols, you can utilize protocol extensions and protocol based programming to add functionality to the various enums that you defined for your game. Here's a short example that tries to display such a hierarchy:

enum FlyingBeast { case Dragon, Hippogriff, Gargoyle }
enum Horde { case Ork, Troll }
enum Player { case Mage, Warrior, Barbarian }
enum NPC { case Vendor, Blacksmith }
enum Element { case Tree, Fence, Stone }

protocol Hurtable {}
protocol Killable {}
protocol Flying {}
protocol Attacking {}
protocol Obstacle {}

extension FlyingBeast: Hurtable, Killable, Flying, Attacking {}
extension Horde: Hurtable, Killable, Attacking {}
extension Player: Hurtable, Obstacle {}
extension NPC: Hurtable {}
extension Element: Obstacle {}

4.8 Battling stringly typed code

In bigger Xcode projects, you're quickly accumulating lots of resources which are accessed by string. We've already mentioned reuse identifiers and storyboard identifiers above, but there's also: Images, Segues, Nibs, Fonts, and other resources. Oftentimes, those resources can be grouped into several distinct sets. If that's the case, a String typed enum is a good way of having the compiler check this for you.

enum DetailViewImages: String {
  case Background = "bg1.png"
  case Sidebar = "sbg.png"
  case ActionButton1 = "btn1_1.png"
  case ActionButton2 = "btn2_1.png"
}

For iOS users, there's also R.swift which auto generates structs for most of those use cases. Sometimes you may need more control though (or you may be on a Mac 8)

4.9 API Endpoints

Rest APIs are a great use case for enums. They're naturally grouped, they're limited to a finite set of APIs, and they may have additional query or named parameters which can be modelled through associated values.

Take, for example, a look at a simplified version of the Instagram API

enum Instagram {
  enum Media {
    case Popular
    case Shortcode(id: String)
    case Search(lat: Float, min_timestamp: Int, lng: Float, max_timestamp: Int, distance: Int)
  }
  enum Users {
    case User(id: String)
    case Feed
    case Recent(id: String)
  }
}

Ash Furrow's Moya library is based around this idea of using enums to map rest endpoints.

4.10 Linked Lists

Airspeed Velocity has a great writeup on how to implement a Linked List with an enum. Most of the code in his post goes far beyond enums and touches a lot of interesting topics 9, but the basis of his linked list looks kinda like this (I simplified it a bit):

enum List {
    case End
    indirect case Node(Int, next: List)
}

Each Node case points to the next case, and by using an enum instead of something else, you don't have to use an optional for the next value to signify the termination of the list.

Airspeed Velocity also wrote a great post about the implementation of a red black tree with indirect Swift enums, so while you're already reading his blog, you may just as well also read this one.

4.11 Settings Dictionaries

This is a very, very smart solution that Erica Sadun came up with. Basically whenever you'd use a dictionary of attribute keys as a way to configure an item, you'd instead use a Set of enums with associated values. That way, the type checker can confirm that your configuration values are of the correct type.

For more details, and proper examples, check out her original blog post.

5 Limitations

We're ending this post, again, with a list of things that don't work yet with enums.

5.1 Retrieving Associated Values

David Owens makes the case that the current way of associated value retrieval is unwieldy. I encourage you to follow the link and read the post, but here's the gist: In order to retrieve the associated values from an enum, you have to use pattern matching. However, associated values are effectively tuples which were attached to the enum case. Tuples, on the other hand, can be deconstructed in a much simpler way simply by using .keyword or .0. i.e:

// Enums
enum Ex { case Mode(ab: Int, cd: Int) }
if case Ex.Mode(let ab, let cd) = Ex.Mode(ab: 4, cd: 5) {
    print(ab)
}
// vs tuples:
let tp = (ab: 4, cd: 5)
print(tp.ab)

If you also feel that this is how we should be able to deconstruct enums, there's a rdar for you: rdar://22704262

5.2 Equatable

Enums with associated values do not conform to the equatable protocol. That's a pity because it makes a lot of things much more cumbersome and verbose than they need to be. The underlying reason is probably that associated values are just tuples internally and tuples do not conform to equatable. However, for a limited subset of cases, namely those where all associated value / tuple types conform to equatable, I think the default case should be that the compiler automatically also generates the equatable extension.

// Int and String are Equatable, so case Ex should also be equatable.
enum Ex { case Mode(ab: Int, cd: String) }

// Swift could auto-generate this func
func == (lhs: Ex.Mode, rhs: Ex.Mode) -> Bool {
    switch (lhs, rhs) {
       case (.Mode(let a, let b), .Mode(let c, let d)):
	   return a == c && b == d
       default:
	   return false
    }
}

5.3 Tuples

The biggest issue is, again, Tuple support. I love tuples, they make many things easier, but they're currently under-documented and cannot be used in many scenarios. In terms of enums, you can't have tuples as the enum value:

enum Devices: (intro: Int, name: String) {
  case iPhone = (intro: 2007, name: "iPhone")
  case AppleTV = (intro: 2006, name: "Apple TV")
  case AppleWatch = (intro: 2014, name: "Apple Watch")
}

This may not look like the best example, but once you start using enums, you'll often end up in situations where you'd like to be able to do something like the above.

5.4 Enumerating Enum Cases

We've already discussed this above. There's currently no good way to get a collection of all the cases in an enum so you can iterate over them.

5.5 Default Associated Values

Another thing which you may run into is that associated values are always types but you can't set a default value for those types. Imagine such an example:

enum Characters {
  case Mage(health: Int = 70, magic: Int = 100, strength: Int = 30)
  case Warrior(health: Int = 100, magic: Int = 0, strength: Int = 100)
  case Neophyte(health: Int = 50, magic: Int = 20, strength: Int = 80)
}

You could still create new cases with different values, but the default settings for your character would be mapped.

6 Changes

10/26/2015

  • Added additional limitation examples (Equatable & Retrieving associated values)

10/22/2015

  • Incorporated PR #6 from @mabidakun.
  • Added internal links
  • Split up the account example into two easier to digest snippets.

10/21/2015

Footnotes:

1
Except by jumping through some hoops, see below
2
This is a simplified implementation for demo purposes. In reality you'd write this with optionals and a reverse argument order. Have a look at popular functional programming libraries like Swiftz or Dollar
3
This example stems straight from Apple's Swift documentation
4
Which make it oftentimes very difficult to discover them
5
This is a simplified version, of course. Swift adds a lot of sugar for you
6
If you ever used JSON in an app, you may well have run into this issue once
7
Btw. You can't use only numbers as enum case names, so case 400 does not work
8
Although Mac support for R.swift seems to be forthcoming
9
Translated: Go there, read it

If you read this far, you should follow me (@terhechte)
on Twitter