Swift Containers
[WARNING] Could not convert TeX math '{3 \choose 2} = 3', rendering as TeX <!DOCTYPE html> Containers

Containers

Swift provides you with three container formats to build your custom types on top of: structs, enums, and classes.

Each type has different implicit guarantees and different runtime characteristics. Let’s cover each container now so we can get experience using them for real applications later.

struct

Structs in Swift are lightweight containers capable of holding multiple properties, each with potentially different types.

Here’s a struct describing basic inventory details of supermarket items:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct SupermarketInventoryItem {
    let name: String
    var aisle: String
    var shelfPosition: Int
    var priceRetail: Int
    var priceWholesale: Int
    var cost: Int
    let firstAvailableDate: String
    var lastAvailableDate: String
    var inStock: Bool
    var showOnline = false
    let SKU: String
    let leadWeeksForReordering = 4
}

With only the definition of the struct above, Swift creates a full constructor for every property. We can create SupermarketInventoryItem structs all day long:

1
2
3
4
SupermarketInventoryItem(name: <#String#>, aisle: <#String#>, shelfPosition: <#Int#>,
    priceRetail: <#Int#>, priceWholesale: <#Int#>, cost: <#Int#>,
    firstAvailableDate: <#String#>, lastAvailableDate: <#String#>, inStock: <#Bool#>,
    showOnline: <#Bool#>, SKU: <#String#>, leadWeeksForReordering: <#Int#>)

(The <# … #> parts are just the type of the argument; you replace those with your actual values when you create the struct.)

Let’s analyze SupermarketInventoryItem in a bit more detail.

let vs. var

Swift gives us two ways to define properties. A property is either variable or fixed. Variable properties (changeable more than once) are defined by var. Fixed properties can only be set once at container initialization time. Any attempt to modify a fixed let property later will be detected by the compiler and result in a compile time error.

Type Declarations

In Swift, types on properties are specified by appending the type to the property name after a colon. This is called annotating the variable with type information.

Note the two properties named showOnline and leadWeeksForReordering. Those properties don’t define a type because we gave them default values. Every time a SupermarketInventoryItem is created, those two properties assume their default values.

Swift has very effective type inference. If you provide default values for all your properties, you do not need to type redundant type information, but you are always welcome to over-specify type information if it makes reading and understanding your data structures easier.

Also note how showOnline is declared var so we can update the showOnline status after the struct is created. On the other hand, leadWeeksForReordering defaults to 4, but you can initialize to a new value when you create your struct. The only restriction is after initialization, any let properties cannot be changed again. It’s perfectly fine to override the default value of 4 with something else on a per-item basis.

Create an Item

We can use the Swift-provided initializer to create a new item:

1
2
3
4
var someTea = SupermarketInventoryItem(name: "tea", aisle: "tea aisle",
    shelfPosition: 3, priceRetail: 499, priceWholesale: 399, cost: 50,
    firstAvailableDate: "2014-01-01", lastAvailableDate: "2014-06-15",
    inStock: true, showOnline: true, SKU: "TEATEATEA", leadWeeksForReordering: 1)

Update an Item

We can update our var properties now too:

1
2
3
someTea.cost = 75
someTea.priceRetail = 599
someTea.priceWholesale = 499

If we try to update a let property, we get:

1
2
3
4
5
6
7
someTea.name = "SUPER TEA"

Playground execution failed: error: <REPL>:30:14: error:
cannot assign to 'name' in 'someTea'

someTea.name = "SUPER TEA"
~~~~~~~~~~~~ ^

Using in Functions

As we saw in the Mars Climate Orbiter example, structs can be passed to functions and returned from functions.

Let’s define a function that takes an item and does some checking:

1
2
3
4
5
6
7
func canPublishToWebsite(item: SupermarketInventoryItem) -> Bool {
    if item.showOnline && item.inStock {
        return true
    } else {
        return false
    }
}

The function accepts one argument named item with type SupermarketInventoryItem and returns one Bool value.

Now we can use our new function to check if we should put this item on our website:

1
publishToWebsite(someTea)

Functions in Structs

But, that function isn’t part of a great data model. The property of publishToWebsite relies entirely on data living inside the struct itself.

Swift allows us to attach functions directly to structs.

Let’s add a function named shouldPublishToWebsite() to our struct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct SupermarketInventoryItem {
    let name: String
    var aisle: String
    var shelfPosition: Int
    var priceRetail: Int
    var priceWholesale: Int
    var cost: Int
    let firstAvailableDate: String
    var lastAvailableDate: String
    var inStock: Bool
    var showOnline = false
    let SKU: String
    let leadWeeksForReordering = 4
    
    func shouldPublishToWebsite() -> Bool {
        if showOnline && inStock {
            return true
        } else {
            return false
        }
    }
}

Note how in the function we refer to properties of the struct directly. We don’t need to refer to them with self.showOnline or self.inStock because Swift knows our properties belong to the struct where the method is defined. You can still use self if you want to though—both approaches work equally well.

Now, entirely contained within the struct, we can just ask:

1
someTea.shouldPublishToWebsite()

Can we update our struct using in-struct functions too?

Let’s add a function to immediately remove an item due to a contaminated food recall:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct SupermarketInventoryItem {
    let name: String
    var aisle: String
    var shelfPosition: Int
    var priceRetail: Int
    var priceWholesale: Int
    var cost: Int
    let firstAvailableDate: String
    var lastAvailableDate: String
    var inStock: Bool
    var showOnline = false
    let SKU: String
    let leadWeeksForReordering = 4
    
    func shouldPublishToWebsite() -> Bool {
        if showOnline && inStock {
            return true
        } else {
            return false
        }
    }
    
    mutating func disableItem() {
        lastAvailableDate = "immediately"
        inStock = false
        showOnline = false
    }
}

Note how the new function disableItem() is annotated with the word mutating. Structs are not allowed to modify their own properties unless the method inside the struct is specified as mutating.

Now with our fancy mutating disable feature, we can:

1
2
3
someTea.lastAvailableDate /* "2014-06-15" */
someTea.disableItem()     /* returns nothing, changes property values */
someTea.lastAvailableDate /* "immediately" */

Revisiting let vs. var

We saw how individual properties can be set to fixed or variable values by using let and var to declare properties, but what about the entire struct itself?

We declared someTea as var, but what if we assign someTea to a let?

1
2
3
let someOtherTea = someTea

someOtherTea.cost = 30

We immediately get an error:

1
2
3
4
5
Playground execution failed: error: <REPL>:64:19: error: cannot assign to 'cost'
in 'someOtherTea'

someOtherTea.cost = 30
~~~~~~~~~~~~~~~~~ ^

If you declare an entire struct as let, the entire struct acts as if each individual property is declared with let regardless of any var usage.

Also note: when we performed someOtherTea = someTea, what happened is every value inside someTea got copied to a new SupermarketInventoryItem held by someOtherTea.

Takeaway: All structs are value types. If you assign a struct to another name, all values of the struct get copied to the new location. Note: this includes passing structs as function arguments. Structs are always copied when they are passed around.

There is no way to have two variables refer to the same underlying struct. Since every struct is a completely isolated container of properties, you can be sure any changes you make to your current struct are isolated and will not change any other parts of your program simultaneously.

enum

Enough structs. How about some enums?

enum is short for enumeration. Many languages have enums, but Swift enums are especially useful for helping you write more clear, more concise, and less error-prone code.

If you are familiar with features called atoms or symbols in other languages, you can think of Swift enums as a named collection of atoms (or symbols).

You can also think of enums as one big named type holding smaller named types inside.

Create an Enum

Let’s define an enum describing the evacuation level around a nuclear reactor:

1
2
3
4
5
6
7
8
9
enum Evacuation {
    case NoDanger
    case DangerRising
    case DangerFalling
    case PreEvacuation
    case EvacuationInProgress
    case EvacuationRecall
    case EmergencyEvacuateASAFP
}

Swift allows you to be succinct when possible, so you could also define Evacuation as:

1
2
3
4
enum Evacuation {
    case NoDanger, DangerRising, DangerFalling, PreEvacuation,
         EvacuationInProgress, EvacuationRecall, EmergencyEvacuateASAFP
}

Unlike enums in C, enums in Swift have no value by default. The value of Evacuation.NoDanger isn’t 0—it has no user-visible value and can only be used for comparisons at this point.

The power of enums come from Swift’s ability to compare enum elements exactly. No two differently-named enum values can ever equal each other. Even if you define two enums with the same inner member names, the inner members of different enums will never be equal because they belong to different outer enum types.

Swift’s ability to enforce strict enum uniqueness removes an entire class of problems from other languages where enums are just shorthand for integers behind the scenes, so passing an integer is equivalent to passing the enum value (or even passing another enum with the same underlying integer value) in those languages.

Let’s play with Evacuation:

1
2
let ok = Evacuation.NoDanger
let bugout = Evacuation.EmergencyEvacuateASAFP

To access an individual value of an enum, you operate on the enum type directly. Enums can take on only one value at a time.

We can easily check for equality:

1
2
ok == bugout              /* false */
ok == Evacuation.NoDanger /* true */

Enum Default Raw Values

But, what if we want our enums to take on values? We can start off by giving our enum raw values in the definition:

1
2
3
4
5
6
7
8
9
enum Evacuation {
    case NoDanger = "ok"
    case DangerRising = "up"
    case DangerFalling = "down"
    case PreEvacuation = "get ready"
    case EvacuationInProgress = "leave now"
    case EvacuationRecall = "come back"
    case EmergencyEvacuateASAFP = "RUN!"
}

Looks good, right? WRONG.

Swift gives us this error:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Playground execution failed: error: <REPL>:6:21: error: enum case cannot have a raw value
if the enum does not have a raw type
    case NoDanger = "ok"
                    ^
<REPL>:7:25: error: enum case cannot have a raw value if the enum does not have a raw type
    case DangerRising = "up"
                        ^
<REPL>:8:26: error: enum case cannot have a raw value if the enum does not have a raw type
    case DangerFalling = "down"
                         ^
<REPL>:9:26: error: enum case cannot have a raw value if the enum does not have a raw type
    case PreEvacuation = "get ready"
                         ^
<REPL>:10:33: error: enum case cannot have a raw value if the enum does not have a raw type
    case EvacuationInProgress = "leave now"
                                ^
<REPL>:11:29: error: enum case cannot have a raw value if the enum does not have a raw type
    case EvacuationRecall = "come back"
                            ^
<REPL>:12:35: error: enum case cannot have a raw value if the enum does not have a raw type
    case EmergencyEvacuateASAFP = "RUN!"

If we give our enum explicit raw values, Swift requires we declare the type for our enum. This also means every default raw value in our enum must have the same type.

1
2
3
4
5
6
7
8
9
enum Evacuation: String {
    case NoDanger = "ok"
    case DangerRising = "up"
    case DangerFalling = "down"
    case PreEvacuation = "get ready"
    case EvacuationInProgress = "leave now"
    case EvacuationRecall = "come back"
    case EmergencyEvacuateASAFP = "RUN!"
}

For raw value assignment, Swift only supports built-in types.

Creating Enum Value at Runtime

Great, so we can create an enum type with guaranteed-to-be-unique members, but how do we use them? Obviously our code can’t pre-decide to use Evacuation.NoDanger statically. We have to update our conditions during runtime.

Since we’ve defined raw values for our enum, Swift automatically attaches a method named fromRaw() to our typed enum. Yes, enums can have functions just like structs can have functions.

Let’s retrieve the proper enum value using only a user-submitted condition string:

1
2
3
let conditionReading = "up"
let evacuationLevel = Evacuation.fromRaw(conditionReading)
evacuationLevel == Evacuation.DangerRising    /* true */

We pass the string “up” to fromRaw() on the Evacuation enum and we get a value back. But, what value do we get back? We passed in a known-good value to fromRaw(), so we should get Evacuation.DangerRising back. But, we don’t.

What we get back is something that, when asked, will say it is an Evacuation.DangerRising, but it’s really something more reliable.

Let’s take a step back and consider what would happen if we passed a string not matching any raw value of Evacuation. What if we passed in string “CASE NIGHTMARE GREEN”? What would Swift return to us then since there’s no matching raw value in the enum?

Swift solves this conundrum by using optionals. Optionals allow Swift to be type-safe against common cases of null pointer dereferences.

If we did ask for fromRaw() using “CASE NIGHTMARE GREEN”, since Swift can’t know ahead of time if you will pass impossible values to fromRaw(), the return value of Evacuation.fromRaw() is not Evacuation, but rather, Evacuation?. The question mark signifies the return type is an optional.

What does optional mean? Optional means the value is either the value declared, in this case an Evacuation, or nothing at all. To check what you got back, you can go through a conditional:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if evacuationLevel {
    println("worked!")
} else {
    println("didn't worked!")
}

let otherLevel = Evacuation.fromRaw("CASE NIGHTMARE GREEN")
otherLevel == nil  /* true */

if otherLevel {
    println("other worked!")
} else {
    println("other didn't worked!")
}

The above code snippet prints “worked!” then “other didn’t worked!”.

You can explicitly see the difference between Evacuation and Evacuation? if you declare types instead of letting Swift infer types for you:

1
let newLevel: Evacuation = Evacuation.fromRaw("oh my glob")

That one tiny line generates this big error:

1
2
3
4
5
6
Playground execution failed: error: <REPL>:47:39: error: value of optional type
'Evacuation?' not unwrapped; did you mean to use '!' or '?'?

let newLevel: Evacuation = Evacuation.fromRaw("oh my glob")
                                      ^
                                                           !

The error is telling you about two choices available to you. You can either declare newLevel as type Evacuation? — or — you can use ! to unwrap the optional directly. If you unwrap an optional actually containing nil, you will trigger a runtime error and your program will crash.

You can remove the error with either of these approaches:

1
2
let tryAgain: Evacuation? = Evacuation.fromRaw("not righteous; wrong-teous")
let areYouSure: Evacuation = Evacuation.fromRaw("RUN!")!

Reading Enum Values

In your Playground, you can just enter the name of your variable and see the contents. In your program, you can’t inspect an enum that way because, well, you don’t have a Playground.

Let’s say we’re reading user input and we want to return a full description of the enum we matched:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
let userInput = "get ready"
var foundEnum: Evacuation

if let evac = Evacuation.fromRaw(userInput) {
    foundEnum = evac
} else {
    println("No enum match!")
    /* Assume bad user data means no danger.
     * What could go wrong? */
    foundEnum = Evacuation.NoDanger
}

func evacDesc(evac: Evacuation) -> String {
    switch evac {
    case .NoDanger:
        return "no danger; you're okay"
    case .DangerRising:
        return "potential danger; stay tuned"
    case .DangerFalling:
        return "danger level falling; you'll be okay"
    case .PreEvacuation:
        return "danger detected; we're getting out of here"
    case .EvacuationInProgress:
        return "run, don't walk, to the evac boats"
    case .EvacuationRecall:
        return "false alarm; everybody come back!"
    case .EmergencyEvacuateASAFP:
        return "GTFO NOW"
    }
}

Note how when Swift knows the context for your enum, you are not required to state the entire enum name when comparing values. You can use .[name] syntax to address the exact property of your enum.

Shorthand enum property syntax even extends to boolean comparisons. Since Swift is strongly typed, Swift knows you are comparing an Evacuation, and it allows you to use shorthand directly in a boolean expression:

1
foundEnum == .PreEvacuation  /* evaluates to true */

Back to testing our new description function; we can now call:

1
evacDesc(foundEnum) /* returns: "danger detected; we're getting out of here" */

Much like the struct example, we can incorporate the description function directly into our enum definition too:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
enum Evacuation: String {
    case NoDanger = "ok"
    case DangerRising = "up"
    case DangerFalling = "down"
    case PreEvacuation = "get ready"
    case EvacuationInProgress = "leave now"
    case EvacuationRecall = "come back"
    case EmergencyEvacuateASAFP = "RUN!"
    
    func desc() -> String {
        switch self {
        case .NoDanger:
            return "no danger; you're okay"
        case .DangerRising:
            return "potential danger; stay tuned"
        case .DangerFalling:
            return "danger level falling; you'll be okay"
        case .PreEvacuation:
            return "danger detected; we're getting out of here"
        case .EvacuationInProgress:
            return "run, don't walk, to the evac boats"
        case .EvacuationRecall:
            return "false alarm; everybody come back!"
        case .EmergencyEvacuateASAFP:
            return "GTFO NOW"
        }
    }
}

Note how we removed the parameter to the function and now switch on self instead of any parameter value.

Now we can run desc() on our enum directly:

1
foundEnum.desc() /* returns "danger detected; we're getting out of here" */

Also, like structs, enums are value types. If you assign an enum to another variable, the current enum value is copied and now you have two enums. Note: also like structs, this includes passing enums as function arguments. Enums are always copied when they are passed around.

There is no way to have two variables refer to the same underlying enum, but this is where classes come into play.

class

Class. Everybody’s got a bit of it, right?

In Swift, classes aren’t as special as they are in other languages. Classes serve three purposes in Swift:

  • A Swift class is the only container type not copied when assigned to a new variable or passed to a method in a parameter.
  • A Swift class can inherit methods and properties from other classes using familiar object oriented semantics.
  • A Swift class can have a specific destruction method so your class can clean up resources or notify other pieces of code before it gets deleted.

Now, this is a book about Swift types. We’re not going to do a deep dive into an entire undergraduate course in object oriented theory and dynamic polymorphism here, but we will cover Swift-specific use cases for Swift’s class type.

Swift Class Basics

Naming

Swift containers (struct, enum, class) all share a bit of common functionality.

You can add custom methods to each container. You can write custom constructors for each container. Structs and enums can adopt protocols just like classes can.

Because everything is operationally similar, Swift doesn’t call an instantiated class an “object,” but rather “instance.”

Finally, a value we can share

Let’s start by defining a class to encompass every piece of work Joss Whedon has ever produced:

1
2
3
4
5
6
7
8
9
class JossWhedonMedia {
    var killsMainCharacters: Bool
    var hasKickinThemeSong: Bool
    var isRepurposedHerosJourney: Bool
    var castOnlyIncludesFriendsOfCreator: Bool
    var usesAppliedPhlebotinum: Bool
    var hasNonsenseFaceHeelTurn: Bool
    var runtime: Int
}

Looks good, right? WRONG.

It looks like a struct, but it’s not a struct, it’s a class.

Swift gives us this nice error message:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Playground execution failed: error: <REPL>:4:7: error: class 'JossWhedonMedia' has no
initializers
class JossWhedonMedia {
      ^
<REPL>:5:9: note: stored property 'killsMainCharacters' without initial value prevents
synthesized initializers
    let killsMainCharacters: Bool
        ^
<REPL>:6:9: note: stored property 'hasKickinThemeSong' without initial value prevents
synthesized initializers
    let hasKickinThemeSong: Bool
        ^
<REPL>:7:9: note: stored property 'isRepurposedHerosJourney' without initial value prevents
synthesized initializers
    let isRepurposedHerosJourney: Bool
        ^
<REPL>:8:9: note: stored property 'castOnlyIncludesFriendsOfCreator' without initial value
prevents synthesized initializers
    let castOnlyIncludesFriendsOfCreator: Bool
        ^
<REPL>:9:9: note: stored property 'usesAppliedPhlebotinum' without initial value prevents
synthesized initializers
    let usesAppliedPhlebotinum: Bool
        ^
<REPL>:10:9: note: stored property 'hasNonsenseFaceHeelTurn' without initial value prevents
synthesized initializers
    let hasNonsenseFaceHeelTurn: Bool
        ^
<REPL>:11:9: note: stored property 'runtime' without initial value prevents synthesized
initializers
    var runtime: Int

A struct is happy to create a default constructor for us so we can define every property at creation time. Classes are a bit more strict. The Swift compiler checks to make sure every value in a class instance is initialized after the constructor returns1.

In this case, we didn’t provide any default values, so Swift is refusing to create a default empty constructor for us. There are two ways to fix this: we can either define a custom init() method to set every property, or we can give all our properties default values.

Let’s give all our properties default values, which will also allow us to remove our type annotations:

1
2
3
4
5
6
7
8
9
10
11
class JossWhedonMedia {
    var killsMainCharacters = false
    var hasKickinThemeSong = true
    var isRepurposedHerosJourney = true
    var castOnlyIncludesFriendsOfCreator = true
    var usesAppliedPhlebotinum = false
    var hasNonsenseFaceHeelTurn = true
    var evilCorporationInLA = true
    var canceledTooSoon = true
    var runtime = 90
}

Now Swift is happy again and the errors are gone.

In other languages, JossWhedonMedia would be marked as an abstract class, but Swift doesn’t have the notion of a class you can’t instantiate.

Let’s define some subclasses of JossWhedonMedia:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Angel: JossWhedonMedia {
    let hasForehead = true
    let hasBadassWesley = true
    let muchBrooding = true
}

class AngelAfterTheFall: Angel {
    let isComic = true
}

class Firefly: JossWhedonMedia {
    let callThisLandThisLand = true
    let suddenButInevitableBetrayal = true
    let handsOfBlue = true
    let leatherJackets = false
    let adorableHat = true
}

class FireflyMovie: Firefly {
    let openingWeekendOvershadowedByJodieFosterMovie = true
    let satisfying = false
}

class AvengersMovieI: JossWhedonMedia {
    let annoyingSidekicks = true
    let kerplodeNYC = true
}

class AvengersSeries: AvengersMovieI {
    let secretAlienInMainCast = true
    let multiplePattons = true
}

class Dollhouse: JossWhedonMedia {
    let muchoPrettyPeople = true
}

class CabinInTheWoods: JossWhedonMedia {
    let includesCabin = true
    let inWoods = true
    let jamesCameronCrossover = true
}

We annotate each subclass of JossWhedonMedia the same way we annotate types of regular parameters.
You can read the parent class syntax as “[child class] is of type [parent class].”

Now, let’s create some instances!

1
2
3
var angel = Angel()
angel.hasForehead          /* true */
angel.killsMainCharacters  /* false */

init

The value for killsMainCharacters is the default from JossWhedonMedia, but the value doesn’t represent the truth for Angel. We need a custom initializer.

Let’s add a custom init function to Angel:

1
2
3
4
5
6
7
8
9
10
class Angel: JossWhedonMedia {
    let hasForehead = true
    let hasBadassWesley = true
    let muchBrooding = true
    init() {
        super.init()
        killsMainCharacters = true
        usesAppliedPhlebotinum = true
    }
}

Now when we create our Angel instance, everything is correct:

1
2
3
4
var angel = Angel()
angel.hasForehead            /* true */
angel.killsMainCharacters    /* true */
angel.usesAppliedPhlebotinum /* true */

Sharing

Classes are the only Swift type with sharing semantics. We can assign our one Angel instance to two (or more) variables:

1
2
3
4
angel.runtime  /* is 90, from parent class */
var stillAngel = angel
angel.runtime = 40
stillAngel.runtime  /* is 40 */

If we were to do the same operation with structs, the second variable would not see the changes made in the first instance since structs (and enums) are value types and get copied when assigned to new names.

deinit

The opposite of a custom initializer is a custom de-initializer. Swift sanely names the de-initializer deinit to act as a foil to the initializer name of init.

If your class has a deinit block, the code in your deinit block will be called immediately before your object is scheduled for deletion. You can use a deinit block to make sure any resources held by your instance are released or just to report to other instances this instance will no longer be valid.

Let’s add a deinit block to AvengersMovieI:

1
2
3
4
5
6
7
8
class AvengersMovieI: JossWhedonMedia {
    let annoyingSidekicks = true
    let kerplodeNYC = true
    
    deinit {
        println("After-Credits Teaser Goes Here")
    }
}

Now, when an instance of AvengersMovieI is deallocated, the instance will print to the console.

We can easily test it works with:

1
2
3
4
5
6
7
8
9
10
11
12
class AvengersMovieI: JossWhedonMedia {
    let annoyingSidekicks = true
    let kerplodeNYC = true
    
    deinit {
        println("After-Credits Teaser Goes Here")
    }
}

var avengers:AvengersMovieI? = AvengersMovieI()
avengers!.kerplodeNYC == true
avengers = nil  /* deinit message printed to runtime console */

Note how we defined avengers as an optional. Only optional types can be set to nil in Swift. Using an optional also requires us to unwrap the optional using ! if we are not checking the optional for nil-ness in an if block.

Also note deinit does not have parens. You use deinit as a bare word followed by a block.

Class Summary

In most object oriented languages, the class is the basic unit of encapsulation. In Swift, classes are just one of three container types available for you. You can pick the most expressive container type for your use case. Many times, for encapsulating simple values or creating custom types, classes are overkill. You can use a simple struct instead and get free a initializer handed to you too.

Unlike other programming languages, Swift classes have no concept of property visibility. Every property you create in a class is accessible by every part of your program.

In Swift, classes are reference counted so one instance may be shared among multiple variables. Structs and enums can never be shared and are always copied when used somewhere else.

In Swift, classes are the only type where inheritance is allowed. All Swift containers may implement Protocols, but only classes can inherit properties and methods from parents.

In Swift, classes are the only type allowed to have a deinit method.

If you don’t need to inherit from a parent, share your object among multiple variables, or cleanup when your instance is destroyed, a struct may be a better choice than a class for your custom types.

Exercises

Structs

The properties of SupermarketInventoryItem only use Swift-provided types. As we saw with the Mars Climate Orbiter, we shouldn’t use built-in types to mean different things.

Setting priceRetail to the number 5 has no meaning directly. What does price = 5 mean?

Exercise: define custom types for every property in SupermarketInventoryItem. To get started, you can define a Price type instead of using Int for prices. Then you can use an actual date type instead of strings for dates (NSDate perhaps). Then you can create a custom aisle data type and shelf position data type for maximum expressiveness.

Try to only use built-in Swift types (numbers, strings) in base types you define, then define your own data structures and program interfaces on top of your own base types.

Enums

In the enum examples, we covered assigning default raw values to enums. Raw values on enums can be any Swift built in number, string, or character type. Default raw values for enum properties can only be set at compile time and can’t be changed after your program is compiled.

But, what if we want our enum properties to have user-defined sub-properties themselves?

In addition to compile-time default raw values for enums, Swift supports binding associated values to enum properties at runtime.

Here’s a tiny example. First, we’ll define the types we want our associated values to use, then we define a Clothing enum in terms of our own types:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct Jacket {
    let id: Int
}
struct Pants {
    let id: Int
}
struct Shirt {
    let id: Int
}
struct Tie {
    let id: Int
}
struct Shoes {
    let id: Int
}

enum NeverNude {
    case Yes, No
}

enum Clothing {
    case Formal(Jacket, Pants, Shirt, Tie, Shoes)
    case Casual(Shirt, Pants)
    case Sleep(NeverNude)
}

Note how the Clothing enum assigns an arbitrary number of types to each enum property. Values on associated types may not have default values since they are designed to be set at runtime (and enums can’t create arbitrary instances as default values). When you use associated values, you do not annotate the entire enum with a type declaration like we had to do when using default raw values.

Also note how we mix types of enums and structs (and classes if we used them here) interchangeably. When you define a container type, you may use the type interchangeably. Swift doesn’t care if you are using a class type or a struct type or an enum type. As long as all your types match and you only perform operations allowed under your type, you’re good to go.

We can create a couple Clothing instances and give each property specific values:

1
2
3
var tired = Clothing.Sleep(NeverNude.No)
var after6pmLemon = Clothing.Formal(Jacket(id: 3), Pants(id: 7), Shirt(id: 12),
    Tie(id: 6002), Shoes(id: 7000005))

How do we get the values out of our properties? You’ll notice the sub-values of each enum property have no names. To access the sub-values, we must use Swift’s positional pattern matching.

Let’s break the values out using a switch statement for pattern matching on associated values:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch tired {
case .Sleep(let nudeness):
    println("You be tired, but at least you're " +
            (nudeness == NeverNude.Yes ? "not " : "") + "naked")
default:
    println("Some other crazy jazz")
}

switch after6pmLemon {
case .Formal(let jacket, let pants, let shirt, let tie, let shoes):
    println("You get the idea, we're not going to print all of these here.")
default:
    println("What am I, a farmer?")
}

Swift switch statements require every case in the enum be considered. To short circuit that limitation when we don’t care about all the inner enum types, we can use a default: case.

Notice how the associated values of each enum type are referenced positionally in each case. Swift automatically infers the type of your matching parameters from the enum definition itself so you’re not constantly required to repeat the same type declarations as boilerplate everywhere.

Once you’re inside a matching case, you can perform actions based on the associated values of your enum then move on to bigger and better things.

Exercise: Define a new enum having properties of the most common locations you visit throughout the week. Home, Work, I-280, I-Wanna-Go-Home, etc. For each enum property, define useful associated values (for bonus points define custom types for all your associated values), then create some enums and extract their associated values using switch statements.

Classes

Classes are typically hailed as super awesome amazeballs, but classes are just another way to define a type. In most object oriented languages, they teach you a “class” represents an “object,” but that’s a lie. A class represents a type and when you instantiate a class, you get an instance with the type of the class you just created. The notion of “object” existing from a static “class” is a bit backwards. When developing, think in terms of classes as types, not classes as oh-so-special-snowflake objects.

Swift’s strict compile-time type checking—with built-in defenses against null pointer dereferencing—gives you more expressability than classes in lesser languages where null pointers flow freely and clearly preventable crashes (software or physical) happen in unconscionable numbers every day.

You are probably more familiar with the concept of classes than concepts of Swift’s struct and enum containers, so just try a basic exercise to make sure you understand how Swift classes work.

Exercise: Create a class hierarchy describing modern day touchscreen devices. Instantiate a few classes representing devices around you. Make sure you understand how init and super work together and how default values for properties differ based on being assigned in initializers versus default raw values versus using optionals.

Here’s a quick template to get you started:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
enum Touchscreen {
    case Resistive, Capacitive, KinectPowered
}

struct Newton {
    /* Newton the SI unit of force, not Newton the 90s tablet */
    let val: Float
}

enum Force: Float {
    case EarthGravity = 9.80665
}

struct Mass {
    struct Grams {
        let val: Float
    }

    let mass: Grams
    
    func weightOnEarth() -> Newton {
        /* F = ma; w = mg */
        return Newton(val: mass.val * Force.EarthGravity.toRaw())
    }
}

class ModernDevice {
    let touchscreen: Touchscreen?
    let dimensions: CGRect?
    let mass: Mass?
   
    init(touch: Bool, dimensions: CGRect?, mass: Mass) {
        touchscreen = touch ? Touchscreen.Capacitive : nil
        self.dimensions = dimensions
        self.mass = mass
    }
    
    convenience init() {
        self.init(touch: false, dimensions: nil,
                  mass: Mass(mass: Mass.Grams(val: 131.8)))
    }
}

class AppleDevice: ModernDevice {

}

class iPhone: AppleDevice {
    
}

class iPhone5: iPhone {
    
}

class iPhone5s: iPhone {
    
}

class iPad: AppleDevice {
    
}

class iPad3rdGen: iPad {
    
}

class iPadMiniRetina1stGen: iPad {
    
}

Container Differences

We’ve covered the three container types mostly in isolation up to this point. Let’s compare the best uses for each type to get a better feel for when to use what 2.

struct vs. enum

Structs and enums are easy to contrast because they have completely opposite use cases.

As we’ve seen, structs hold multiple properties where properties can have multiple types across the entire struct, and all properties of a struct are accessible at the same time.

Enums also have multiple properties, but each property has an implicit user-hidden value. Each enum property is guaranteed to be globally unique. Only one property of an enum may be active at once for a given instance of the enum you’ve created.

If you assign default raw values to every property of your enum, every default value must be of the same type, and you must annotate the type of your enum with the common type of your default values.

Quick enum default raw value review:

1
2
3
4
5
6
7
8
9
10
enum SomeStrings: String {
    case Banana = "banana"
    case Aguacate = "aguacate"
    case Pompadour = "pompadour"
}

enum SomeChars: Character {
    case A = "a"
    case Z = "z"
}

Structs and enums are both value types meaning every time you assign an existing struct or enum to another name, the values get copied.

Copy example:

1
2
3
4
5
6
7
8
9
10
11
var aOrZ = SomeChars.A
aOrZ == SomeChars.A  /* true */

var zOrA = aOrZ
zOrA == SomeChars.A  /* true */

aOrZ = .Z /* assignment */

aOrZ == .Z /* true */

zOrA == .Z /* false, zOrA is independent copy, so no shared updating */

You’ll want to use enums when representing states that can only take one of multiple values at a time — or — when you want to store a type-safe way to refer to common fixed-value data points.

For example, you could store constants in an enum container based on their units:

1
2
3
4
5
6
7
8
9
10
11
12
13
enum PhysicsSI: Float {
    case EarthGravity = 9.80665
    case Hubble = 2.5e-18
    case G = 6.673e-11
    case Boltzmann = 1.38054e-23
}

enum PhysicsAmerican: Float {
    case EarthGravity = 32.1740
    case Hubble = 2.163e-37
    case G = 3.44e-8
    case Boltzmann = 7.2694e-27
}

Enums are a great way to organize constants. They give you a stable namespace for common data types as well as compile-time checking for accuracy when passing the enum properties as arguments to functions.

struct vs. class

Structs and classes are two very similar things in Swift.

All you need to remember about the differences between a struct and a class is:

  • Structs are best used to store groups of closely related properties
    • struct properties don’t allow an inheritance hierarchy or sharing of data.
  • Structs automatically generate a constructor so you can initialize all properties in your struct without writing boilerplate initialization setters.
  • Classes are best used if your data structures are naturally hierarchical in nature
    • or if you need to share one instance of a class among various parts of code.
    • or if you need to inherit methods or properties from parent classes
  • Structs are copied every time they are passed to methods or assigned to new variable names. If you use large structs, the extra copying could impact your performance.
  • Class instances are passed by reference. When an instance is passed to a method, only a tiny reference is given to the method.
  • Classes are the only way to implement data structures requiring one instance remain accessible by multiple other instances (examples: circular doubly linked lists, various tree/graph structures, etc).
  • Classes are the only container type to have an optional deinit block.

class vs. enum

Classes and enums are the most different of Swift’s containers.

Classes can have multiple properties, inheritance, and instances of classes are passed by reference.

Eunms have multiple possible properties, but only one property is ever active at a given time for a given instantiation of the enum. Enums are passed by value, so every time you use an enum as an argument, the value is copied (which is perfectly okay since your enum only holds one tiny value active at once).

Classes can approximate enum behavior by using static fixed properties with specialized accessors, but enums take care of those common situations for you.

Common Among Everything

All Swift container types have some common capabilities.

Each container type can have an init() method to bring up your new instance. Each container type can have convenience init() methods for helping to provide default values when creating new instances.

Each container type can adopt (conform) to protocols specifying required methods and properties on all instances of a conforming type.

Each container type can hold methods inside of it to operate on the value of the instance directly.

Each container, by definition, creates a new global Swift type with the name you give your container. Swift does not discriminate between the underlying container of a type—Swift only cares about matching types.

Every Swift container may have methods and dynamic properties extended at runtime with Swift’s extension capability.

Try to reason about your program using custom-defined types, not built-in types or only classes you define. Classes are types first and a way to instantiate instances of themselves second.


  1. We could get around this restriction by declaring every type as an optional, but we don’t want to confuse anyone with unwrapping, automatic unwrapping, or adding a bunch of if val { val! } clauses. Plus, there’s another entire type layer of implicitly unwrapped optionals we’re not going to cover here, though we’ve seen implicitly unwrapped optionals in action when we used an optional as an if boolean.

  2. We comparing three things (struct, enum, class) in pairs regardless of order (comparing struct vs. enum is the same as comparing enum vs. struct here), so the number of comparisons we need is ${3 \choose 2} = 3$ comparisons total. If that equation isn’t rendered as math, then your e-reader doesn’t support the current specification requiring support for inline math by default in eBook containers.