Getting to Know Enums, Structs and Classes in Swift

Ray Fix

Update 9/18/16: This tutorial has been updated for Xcode 8 and Swift 3.

enums-structs-classes-feature

Back in the days when there was only Objective-C, encapsulation was limited to working with classes. However, in modern iOS and Mac programming there are three choices: enums, structs and classes in Swift.

Combined with protocols, these types make it possible to create amazing things. While they share many common abilities, these types also have important differences.

The objective of this tutorial is to:

  • Give you some experience using enums, structs and classes.
  • Gain some intuition about when to use them.
  • Give you an understanding of how each works.

In terms of prerequisites, this tutorial assumes that you have at least a little Swift and object-oriented programming experience.

It’s All About Those Types

Three big selling points of Swift are its safety, speed and simplicity.

Safety implies that it’s difficult to accidentally write code that runs amok, corrupting memory and producing hard-to-find bugs. Swift makes your work safer because it tries to make it obvious when you have a bug by showing you problems at compile time, rather than hanging you out to dry at runtime.

Moreover, because Swift lets you clearly express your intent, the optimizer can go to town to help your code run lightning fast.

The Swift language core is simple and highly-regularized, thanks to being built upon a surprisingly small number of concepts. Despite its relatively simple rules, you can do amazing things.

The key to making this happen is the Swift type system:

Diagram showing the six types in Swift

Swift types are powerful, despite there being only six of them. That’s right, unlike many other languages that have literally dozens of built-in types, Swift only has six.

These consist of four named types: protocol, enum, struct, class. There are two compound types as well: tuple, function.

There are those other things you might think of as basic types—like Bool, Int, UInt, Float, Double, Character, String, Array, Set, Dictionary, Optional, etc. However, these are actually built up from the named types and delivered as part of the Swift Standard Library.

This tutorial focuses on the so-called named model types which consist of enum, struct and class.

Shapes with Scalable Vector Graphics (SVG)

As a working example, you’ll build a safe, speedy and simple SVG shape (scalable vector graphics) rendering framework.

SVG is an XML-based vector image format for 2D graphics. This specification has been an open standard developed by the W3C since 1999. (https://developer.mozilla.org/en-US/docs/Web/SVG)

Getting Started

Create a new playground in Xcode to follow along by selecting File\New\Playground… and name it Shapes and set the platform as OS X. Click Next to choose a location to save it, then Create to save the file. Clear the file completely and then enter the following:

import Foundation

Your goal will be able to render something like this:

<!DOCTYPE html><html><body><svg width='250' height='250'><rect x='110.0' y='10.0' width='100.0' height='130.0' stroke='teal' fill='aqua' stroke-width='5' /><circle cx='80.0' cy='160.0' r='60.0' stroke='red' fill='yellow' stroke-width='5'  /></svg></body></html>

Using a WebKit view, it looks like this:

shapes

You’ll need a representation for colors. SVG uses the CSS3 color type that can be specified as a name, RGB or HSL. The full specification is here: http://www.w3.org/TR/css3-color/.

To use a color in SVG, you specify it as an attribute of part of your drawing. For example, fill = 'gray'. An easy approach to represent this in Swift this would be just to use a String as in, let fill = "gray".

String for your Color type might not be such a great idea.

While using String is easy and does the job, there are some major downsides.

  1. It’s error prone. Any strings that are not part of the color spec will compile fine but not show up correctly at runtime. For example, “grey” spelled with an “e” doesn’t work.
  2. Autocomplete won’t help you find valid color names.
  3. When you pass around a color as a parameter, it might not always be obvious that the string is a color.

Enum to the Rescue

Using a custom type solves these problems. If you’re coming from Cocoa Touch, you might think to implement an encapsulated class like UIColor. While using a class design could work, Swift gives you more choices for how to define your model.

Without typing anything in just yet, let’s have a think about how you might implement the colors as an enum.

You might think to implement it like so:

enum ColorName {
  case black
  case silver
  case gray
  case white
  case maroon
  case red
  // ... and so on ... 
}

The above works very similarly to a set of C-style enums. However, unlike C-style enums, Swift gives you the option to specify a type to represent each case.

Enumerations that explicitly specify a backing store type are referred to as RawRepresentable, because they automatically adopt the RawRepresentable protocol.

So, you can specify the type of ColorName as String, and assign a value to each case, like so:

enum ColorName: String {
  case black  = "black"
  case silver = "silver"
  case gray   = "gray"
  case white  = "white"
  case maroon = "maroon"
  case red    = "red"
  // ... and so on ... 
}

However, Swift does something special for enums with a String representation. If you don’t specify what the case is equal to, the compiler automatically makes the String the same as the name of the case. That means you only need to write the case name:

enum ColorName: String {
  case black
  case silver
  case gray
  case white
  case maroon
  case red
  // ... and so on ... 
}

You can further reduce your typing by separating the cases with commas using the keyword case just once.

Add the following code to the end of your playground:

enum ColorName: String {
  case black, silver, gray, white, maroon, red, purple, fuchsia, green, lime, olive, yellow, navy, blue, teal, aqua
}

Now you have a first class custom type and all the goodness that comes with that. For example,

let fill = ColorName.grey   // ERROR: Misspelled color names won't compile. Good!
let fill = ColorName.gray   // Correct names autocomplete and compile. Yay!

Associated Values

ColorName is good for named colors, but you might recall that CSS colors have several representations: named, RGB, HSL and more. How do you model those?

Enums in Swift are great for modeling things that have one of a number of representations, such as CSS color, and each enum case can be paired with its own data. These data are called associated values.

Define the CSSColor using an enum by adding this to your playground:

enum CSSColor {
  case named(ColorName)
  case rgb(UInt8, UInt8, UInt8)
}

With this definition, you give the CSSColor model one of two states.

  1. It can be named, in which case the associated data is a ColorName value
  2. It can be rgb, in which case the associated data is three UInt8 (0-255) numbers for red, green, and blue.

Note that this example leaves out rgba, hsl and hsla cases for brevity.

Protocols and Methods with an Enum

Any model type in Swift can adopt a protocol

You want to be able to print out multiple instances of CSSColor.

In Swift, enums, just like the other named types, can adopt protocols. In particular, your type can magically work with the print statement by adopting the CustomStringConvertible protocol.

The key to inter-operating with the Swift Standard Library is to adopt standard library protocols.

Add the following extension for CSSColor to your playground:

extension CSSColor: CustomStringConvertible {
  var description: String {
    switch self {
    case .named(let colorName):
      return colorName.rawValue
    case .rgb(let red, let green, let blue):
      return String(format: "#%02X%02X%02X", red,green,blue)
    }
  }
}

This makes CSSColor conform to CustomStringConvertible. This is what tells Swift that our type, CSSColor, can be converted to a string. We tell it how by implementing the description computed property.

In this implementation, self is switched upon to determine if the underlying model is a named or RGB type. In each case you convert the color to the required string format for that case. The named case just returns the string name whereas the RGB case returns the red, green and blue values in the required format.

Add the following to your playground:

let color1 = CSSColor.named(.red)
let color2 = CSSColor.rgb(0xAA, 0xAA, 0xAA)
print("color1 = \(color1), color2 = \(color2)") // prints color1 = red, color2 = #AAAAAA

Everything is type checked and proven correct at compile time, unlike how it goes when you use only String values to represent colors.

Note: While you could go back to your previous definition of CSSColor and modify it, you don’t have to. You’ve used an extension to re-open the color type and adopt a new protocol.

The extension style is nice because it makes what you define in order to conform to a given protocol fully explicit. In the case of CustomStringConvertible, you’re required to implement a getter for a description string property.

Initializers with an Enum

Just like classes and structs in Swift, you can also add custom initializers to enum. For example, you can make a custom initializer for grayscale values.

Add this extension to your playground:

extension CSSColor {
  init(gray: UInt8) {
    self = .rgb(gray, gray, gray)
  }
}

Add the following to your playground:

let color3 = CSSColor(gray: 0xaa)
print(color3)  //  prints #AAAAAA

You can now conveniently create grayscale colors!

Namespaces with Enum

Named types can act as a namespace to keep things organized and minimize complexity. You created ColorName and CSSColor, and yet ColorName is only ever used in the context of a CSSColor.

Wouldn’t it be nice if you could hide ColorName within a CSSColor model?

Well, you can! Remove ColorName from your playground and replace it with the following code:

extension CSSColor {
    enum ColorName: String {
        case black, silver, gray, white, maroon, red, purple, fuchsia, green, lime, olive, yellow, navy, blue, teal, aqua
    }
}

This moves ColorName into an extension on CSSColor. Now ColorName is tucked away, and the inner type is defined on CSSColor.

Note: One of the great features of Swift is that the order in which you declare things usually doesn’t matter. The compiler scans the file multiple times and figures it out without needing to forward declare things as it does when working with C/C++/Objective-C.

However, if you receive an error in your playground about ColorName being an undeclared type, move the above extension to just below your enum definition of CSSColor to clear the playground error.

Sometimes playgrounds are sensitive to the ordering of definitions, even when it doesn’t really matter. :]

Enums can be set up as pure namespaces that users can’t accidentally instantiate. For example, you’ll soon need the math constant phi to perform some computations.

Add the following code to the end of your playground:

enum Math {
  static let phi = 1.6180339887498948482 // golden mean
}

Since the Math enum contains no cases, and it’s illegal to add new cases in an extension, it can never be instantiated. You’ll never be able to accidentally misuse Math as a variable or parameter.

By declaring phi as a static constant, you don’t need to instantiate one. Whenever you need the value of the golden ratio, phi, you can simply use Math.phi rather than remembering all those digits!

Taking Stock of Enums

Enums are much more powerful in Swift than they are in other languages, such as C or Objective-C. As you’ve seen, you can extend them, create custom initializer methods, provide namespaces and encapsulate related operations.

So far you’ve used enum to model CSS colors. This works well because CSS colors are a well understood, fixed W3C specification.

Enumerations are great for picking items from a list of well-known things, such as days of the week, faces of a coin or states in a state machine. It’s no surprise that Swift optionals are implemented in terms of an enum with a state of .none or .some with an associated value.

On the other hand, if you wanted CSSColor to be user extensible to other color space models that are not defined in the W3C specification, an enumeration is not be the preferred abstraction.

Which, by the way, brings you to the next Swift named model type, Structures. :]

Using Structs

Because you want your users to be able to define their own custom shapes within the SVG, using an enum is not a good choice for defining shape types.

New enum cases cannot be added later in an extension. That leaves either a class or a struct.

The Swift Standard Library team suggests that when you create a new model, you should first design the interface using a protocol. You want your shapes to be drawable, so add this to your playground:

protocol Drawable {
  func draw(with context: DrawingContext)
}

The protocol defines what it means to be Drawable. It has a draw method that draws to something called a DrawingContext.

Speaking of DrawingContext, it’s just another protocol. Add it to your playground as follows:

protocol DrawingContext {
  func draw(circle: Circle)
  // more primitives will go here ...
}

A DrawingContext knows how to draw pure geometric types: Circle, Rectangle and other primitives. Take note of something here: the actual drawing technology is not specified, but you could implement it in terms of anything — SVG, HTML5 Canvas, Core Graphics, OpenGL, Metal, etc.

You’re ready to define a circle that adopts the Drawable protocol. Add this to your playground:

struct Circle : Drawable {
  var strokeWidth = 5
  var strokeColor = CSSColor.named(.red)
  var fillColor = CSSColor.named(.yellow)
  var center = (x: 80.0, y: 160.0)
  var radius = 60.0
 
  // Adopting the Drawable protocol.
 
  func draw(context: DrawingContext) {
    context.draw(self)
  }
}

In a struct, you group together stored properties. Here you have implemented the following properties:

  • strokeWidth: The width of the line.
  • strokeColor: The color of the line.
  • fillColor: The color to fill the circle with.
  • center: The center point of the circle.
  • radius: The radius of the circle.

Structs work a lot like classes with a couple of key differences. Perhaps the biggest difference is that structs are value types and classes are reference types.

Value vs. Reference Types

value_type

Value types work as separate and distinct entities.

The quintessential value type is an integer because it works that way in most programming languages. If you want to know how a value type acts, ask the question, “What would Int do?” For example:

For Int:

var a = 10
var b = a
a = 30     // b still has the value of 10.
a == b     // false

For Circle (defined using struct):

var a = Circle()
a.radius = 60.0
var b = a
a.radius = 1000.0  // b.radius still has the value 60.0

reference_type

If you had made your circle from a class type, it would have been given reference semantics. That means that it references an underlying shared object.

For Circle (defined using class):

var a = Circle()  // a class based circle
a.radius = 60.0
var b = a
a.radius = 1000.0  // b.radius also becomes 1000.0

When creating new objects using value types, copies are made; when using reference types, the new variable refers to the same object. This dissimiliarity in behavior is a critical difference between class and struct.

Rectangle Model

Add this to your playground to continue making your drawing library by creating a rectangle type:

struct Rectangle : Drawable {
  var strokeWidth = 5
  var strokeColor = CSSColor.named(.teal)
  var fillColor = CSSColor.named(.aqua)
  var origin = (x: 110.0, y: 10.0)
  var size = (width: 100.0, height: 130.0)
 
  func draw(with context: DrawingContext) {
    context.draw(self)
  }
}

You also need to update the DrawingContext protocol so that it knows how to draw a rectangle. Update DrawingContext in your playground so it reads:

protocol DrawingContext {
  func draw(_ circle: Circle)
  func draw(_ rectangle: Rectangle)
  // more primitives would go here ...
}

Circle and Rectangle adopt the drawable protocol. They defer the actual work to something that conforms to the DrawingContext protocol.

Now it’s time to make a concrete model that does drawing SVG style. Add this to your playground:

final class SVGContext : DrawingContext {
 
  private var commands: [String] = []
 
  var width = 250
  var height = 250
 
  // 1
  func draw(_ circle: Circle) {
    commands.append("<circle cx='\(circle.center.x)' cy='\(circle.center.y)\' r='\(circle.radius)' stroke='\(circle.strokeColor)' fill='\(circle.fillColor)' stroke-width='\(circle.strokeWidth)'  />")
  }
 
  // 2
  func draw(_ rectangle: Rectangle) {
    commands.append("<rect x='\(rectangle.origin.x)' y='\(rectangle.origin.y)' width='\(rectangle.size.width)' height='\(rectangle.size.height)' stroke='\(rectangle.strokeColor)' fill='\(rectangle.fillColor)' stroke-width='\(rectangle.strokeWidth)' />")
  }
 
  var svgString: String {
    var output = "<svg width='\(width)' height='\(height)'>"
    for command in commands {
      output += command
    }
    output += "</svg>"
    return output
  }
 
  var htmlString: String {
    return "<!DOCTYPE html><html><body>" + svgString + "</body></html>"
  }
}

SVGContextis a class that wraps a private array of command strings. In sections 1 and 2, you conform to the DrawingContext protocol, and the draw methods just append a string with the correct XML for rendering the shape.

Finally, you need a document type that can contain many Drawable objects, so add this to your playground:

struct SVGDocument {
  var drawables: [Drawable] = []
 
  var htmlString: String {
    let context = SVGContext()
    for drawable in drawables {
      drawable.draw(context)
    }
    return context.htmlString
  }
 
  mutating func append(_ drawable: Drawable) {
    drawables.append(drawable)
  }
}

Here, htmlString is a computed property on SVGDocument that creates an SVGContext and returns the htmlString from the context.

Show Me Some SVG

How about we finally draw an SVG? Add this to your playground:

var document = SVGDocument()
 
let rectangle = Rectangle()
document.append(rectangle)
 
let circle = Circle()
document.append(circle)
 
let htmlString = document.htmlString
print(htmlString)

This creates a default circle and rectangle and puts them into a document, and then it prints the XML.

Let’s now visualize the SVG. Add the following to the end of the playground:

import WebKit
import PlaygroundSupport
let view = WKWebView(frame: CGRect(x: 0, y: 0, width: 250, height: 250))
view.loadHTMLString(htmlString, baseURL: nil)
PlaygroundPage.current.liveView = view

This does some Playground trickery and sets up a web view to view the SVG. Press Command-Option-Return to show this web view in the assistant editor.

Using Classes

So far, you used a combination of structs (value types) and protocols to implement drawable models.

Now it’s time to play with classes too. They let you define base classes and derived classes. The more traditional object-oriented approach to the shapes problem is to make a Shape base class with a draw() method.

Even though you won’t use it now, it’s helpful to know how this approach would work. It would look something like this:

hierarchy

And in code, it would look like the following block — this is just for reference, so don’t add it to your playground:

class Shape {
  var strokeWidth = 1
  var strokeColor = CSSColor.named(.black)
  var fillColor = CSSColor.named(.black)
  var origin = (x: 0.0, y: 0.0)
  func draw(with context: DrawingContext) { fatalError("not implemented") }
}
 
class Circle: Shape {
  override init() {
    super.init()
    strokeWidth = 5
    strokeColor = CSSColor.named(.red)
    fillColor = CSSColor.named(.yellow)
    origin = (x: 80.0, y: 80.0)
  }
 
  var radius = 60.0
  override func draw(with context: DrawingContext) {
    context.draw(self)
  }
}
 
class Rectangle: Shape {
  override init() {
    super.init()
    strokeWidth = 5
    strokeColor = CSSColor.named(.teal)
    fillColor = CSSColor.named(.aqua)
    origin = (x: 110.0, y: 10.0)
  }
 
  var size = (width: 100.0, height: 130.0)
  override func draw(with context: DrawingContext) {
    context.draw(self)
  }
}

In order to make object oriented programming safer, Swift introduced the override keyword. It requires that you, the programmer, acknowledge when you’re overriding something.

It prevents accidentally shadowing existing methods or not properly overriding what you thought you were. It can be a lifesaver when consuming a new version of a library and understanding how things have changed.

line

Nevertheless, there are some drawbacks to this object oriented approach.

The first problem you’ll notice is in the base-class implementation of draw. Shape wants to avoid being misused, so it calls fatalError() to alert derived classes that they need to override this method.

Unfortunately, this check happens at runtime time and not compile time.

Secondly, the Circle and Rectangle classes have to deal with the initialization of the base class data. While this is a relatively easy scenario, class initialization can become a somewhat involved process in order to guarantee correctness.

Thirdly, it can be tricky to future proof a base-class.

For example, suppose you wanted to add a drawable Line type. In order to work with your existing system, it would have to derive from Shape, which is a little bit of a misnomer.

Moreover, your Line class needs to initialize the base class’s fillColor property, and that doesn’t really make sense for a line.

Based on this, you could refactor your hierarchy and it would work better. In practice, however, it might not be possible to modify your base class without breaking many existing clients, and it’s usually impossible to get it right the first time.

Finally, classes have the reference (shared) semantics that were discussed earlier. While Automatic Reference Counting (ARC) takes care of things most of the time, you need to be careful not to introduce reference cycles, or you’ll end up with memory leaks.

If you add the same shape to an array of shapes, it might be surprising when you modify the color of one shape to red and another one also seems to randomly change.

Why Even Use a Class?

Given the above downsides, you might be wondering why you would ever want to use a class.

For starters, they allow you to adopt mature and battle-tested frameworks like Cocoa and Cocoa Touch.

Additionally, classes do have more important uses. For example, a large memory-hogging, expensive-to-copy object is a good candidate for wrapping in a class. Classes can model an identity well. You may have a situation where many views are displaying the same object. If that object is modified, all of the views also reflect changes in the model. With a value type, synchronizing updates can become an issue.

See where I’m going? Classes are helpful anytime reference vs. value semantics come into play.

Check out this two-part tutorial on the subject: Reference vs. Value Types in Swift.

Computed Properties

All named model types let you create custom setters and getters that don’t necessarily correspond to a stored property.

Suppose you want to add a diameter getter and setter to your Circle model. It’s easy to implement it in terms of the existing radius property.

Add the following code to the end of your playground:

extension Circle {
  var diameter: Double {
    get {
      return radius * 2
    }
    set {
      radius = newValue / 2
    }
  }
}

This implements a new computed property which is purely based on the radius. When you get the diameter, it returns the radius doubled. When you set the diameter, it sets the radius to the value divided by 2. Simple!

More often than not, you only want to implement a special getter. In this case, you don’t have to include the get {} keyword block and can just specify the body. Perimeter and area are good use cases for this.

Add the following to the circle extension you just added:

// Example of getter-only computed properties
var area: Double {
  return radius * radius * Double.pi
}
var perimeter: Double {
  return 2 * radius * Double.pi
}

Unlike classes, struct methods are not allowed to modify — aka mutate — stored properties by default, but they can if you declare them as mutating.

For example, add the following to the Circle extension:

func shift(x: Double, y: Double) {
  center.x += x
  center.y += y
}

This tries to define a shift() method on circle which moves the circle in space. i.e. it changes the center point.

But this throws the following error on the two lines which increment the center.x and center.y properties.

// ERROR: Left side of mutating operator has immutable type ‘Double'

This can be fixed by adding the mutating keyword, like so:

mutating func shift(x: Double, y: Double) {
  center.x += x
  center.y += y
}

This tells Swift that it’s OK that your function mutates the struct.

Retroactive Modeling and Type Constraining

One of the great features of Swift is retroactive modeling. It lets you extend behavior of a model type even if you don’t have the source code for it.

Here’s a use case: Suppose you’re a user of the SVG code and you want to add an area and perimeter property to Rectangle just like Circle.

To see what this all means, add this to your playground:

extension Rectangle {
  var area: Double {
    return size.width * size.height
  }
  var perimeter: Double {
    return 2 * (size.width + size.height)
  }
}

Previously, you used extension to add methods to an existing model, and now, you’ll formalize these methods into a new protocol.

Add this to your playground:

protocol ClosedShape {
  var area: Double { get }
  var perimeter: Double { get }
}

That gives you an official protocol.

Next, you’ll tell the circle and rectangle to adopt this protocol retroactively by adding the following to your playground:

extension Circle: ClosedShape {}
extension Rectangle: ClosedShape {}

You can also define a function that, for example, computes the total perimeter of an array of models (any mix of structs, enums, classes) that adopt the ClosedShape protocol.

Add the following to the end of the playground:

func totalPerimeter(shapes: [ClosedShape]) -> Double {
  return shapes.reduce(0) { $0 + $1.perimeter }
}
totalPerimeter([circle, rectangle])

This uses reduce to calculate the sum of perimeters. You can learn more about how it works in An Introduction to Functional Programming.

Where To Go From Here?

The completed playground can be found here.

In this tutorial, you learned about enum, struct and class — the named model types of Swift.

All three have key similarities: they provide encapsulation, can have initializer methods, can have computed properties, can adopt protocols, and they can be modeled retroactively.

However, they also have important differences.

Enums are value types that have a set of cases, where each case can have different associated values. Each value of an enum type represents a single case as defined by the enum. They can’t have any stored properties.

Structs, like enums, are value types but can also have stored properties.

Classes, like structs, can have stored properties, and they can be built into class hierarchies that override properties and methods. Because of this, explicit initialization of base classes is a requirement.

But unlike structs and enums, classes use reference, aka sharing, semantics.

For much more information on this, see the two-part series mentioned earlier, Reference vs. Value Types in Swift.

I hope you have enjoyed this whirlwind tour of the named model types in Swift. If you’re looking for a challenge, consider building a more complete version of the SVG rendering library. You’re off to a good start. :]

As always, if you have questions or insights you would like to share, please use the forums below!

Ray Fix

Ray Fix is using Swift to help create Revolve--the next generation in research microscopy at Echo Laboratories. In addition to his role as Swift Team Lead for Ray Wenderlich, he organizes several iOS/Swift meetups around Southern California.

Ray is mostly-fluent in spoken and written Japanese and stays healthy by walking, jogging, and playing ultimate frisbee. When he is not doing one of those things, he is writing and dreaming of code in Swift. よろしくお願い致します。😃

Other Items of Interest

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 19 total!

Swift Team

... 16 total!

iOS Team

... 30 total!

Android Team

... 15 total!

macOS Team

... 11 total!

Apple Game Frameworks Team

... 10 total!

Unity Team

... 11 total!

Articles Team

... 11 total!

Resident Authors Team

... 11 total!