Swift

Here’s what Apple says about Swift: “Swift is a powerful and intuitive programming language for macOS, iOS, watchOS, tvOS and beyond. Writing Swift code is interactive and fun, the syntax is concise yet expressive, and Swift includes modern features developers love. Swift code is safe by design, yet also produces software that runs lightning-fast.” Sounds great, right? Let’s check it out.

Overview

Swift:

Useful and important resources:

Getting Started

You can write Swift code:

Basic Command Line Scripts

Let's write and run some little scripts. Fire up your favorite editor and continue the tradition:

hello.swift
print("Hello, world")
$ swift hello.swift
Hello, world

Let’s move on to using for-loops, if-statements, and string interpolation:

triple.swift
for c in 1...40 {
    for b in 1..<c {
        for a in 1..<b {
            if a * a + b * b == c * c {
                print("(\(a), \(b), \(c))")
            }
        }
    }
}
$ swift triple.swift
(3, 4, 5)
(6, 8, 10)
(5, 12, 13)
(9, 12, 15)
(8, 15, 17)
(12, 16, 20)
(15, 20, 25)
(7, 24, 25)
(10, 24, 26)
(20, 21, 29)
(18, 24, 30)
(16, 30, 34)
(21, 28, 35)
(12, 35, 37)
(15, 36, 39)
(24, 32, 40)

And then work with variable declarations (both let and var), command line arguments, while-loops, and tuples:

fib.swift
// Writes Fibonacci numbers up to and including the first commandline argument.

let n = Int(CommandLine.arguments[1]) ?? 0

var (a, b) = (0, 1)
while b <= n {
    print(b, terminator: " ")
    (a, b) = (b, a+b)
}
print("")
$ swift fib.swift 300
1 1 2 3 5 8 13 21 34 55 89 144 233
$ swift fib.swift dog

$ swift fib.swift
fatal error: Array index out of range

Time for word counting. This script reads from standard input, counts all words, lowercased (where words are Unicode letters plus the apostrophe and the right single quote), and prints them to standard output together with their counts ordered by frequency descending:

wordcount.swift
import Foundation

var counts = [String: Int]()

while let line = readLine()?.lowercased() {
    var wordCharacters = CharacterSet.letters
    wordCharacters.insert(charactersIn: "'’")
    let words = line.components(separatedBy: wordCharacters.inverted).filter{$0 != ""}
    for word in words {
        counts[word] = (counts[word] ?? 0) + 1
    }
}

for (word, count) in (counts.sorted {$0.1 > $1.1}) {
    print("\(word) \(count)")
}

Let’s try it out on Project Gutenberg’s The Count of Monte Cristo, by Alexandre Dumas, père:

$ curl http://www.gutenberg.org/files/1184/1184-0.txt | swift wordcount.swift
the 28635
of 12919
to 12898
and 12571
a 9472
i 8377
you 8280
.
.
.
brails 1
filthy 1
certificates 1
revolutionists 1
deserts 1
dauzats’ 1
dandy’s 1
paled 1

And let’s ramp it up a bit illustrating arrays, writing your own functions, argument labels, and inout parameters:

anagrams.swift
import Foundation

func generatePermutations(of a: inout [Character], upTo n: Int) {
    if n == 0 {
        print(String(a))
    } else {
        for i in 0..<n {
            generatePermutations(of: &a, upTo: n - 1)
            a.swapAt(n % 2 == 0 ? 0 : i, n)
        }
        generatePermutations(of: &a, upTo: n - 1)
    }
}

if CommandLine.arguments.count != 2 {
    fputs("Exactly one argument is required\n", stderr)
    exit(1)
}
let word = CommandLine.arguments[1]
var charArray = Array(word)
generatePermutations(of: &charArray, upTo: charArray.count - 1)

The REPL

Run the swift command in your terminal with no arguments to bring up a standard REPL:

$ swift
Welcome to Apple Swift version 5.2.4 (swiftlang-1103.0.32.9 clang-1103.0.32.53).
Type :help for assistance.
  1> 2 + 2
$R0: Int = 4
  2> 2 > 2
$R1: Bool = false
  3> var greeting = "Hello, world"
greeting: String = "Hello, world"
  4> let numbers = [50, 20, 30, 90]
numbers: [Int] = 4 values {
  [0] = 50
  [1] = 20
  [2] = 30
  [3] = 90
}
  5> numbers.count
$R2: Int = 4
  6> let poem = """ 
  7.     This is not a Hai 
  8.     ku but I tried to make 
  9.     it one but failed 
 10.     """
poem: String = "This is not a Hai\nku but I tried to make\nit one but failed"
 11> func triple(x: Int) -> Int {
 12.     return x * 3 
 13. } 
 14> triple(x: 300)
$R3: Int = 900
 15> numbers.map(triple)
$R4: [Int] = 4 values {
  [0] = 150
  [1] = 60
  [2] = 90
  [3] = 270
}
 16> $R4[2]
$R5: Int = 90

Xcode Playgrounds

REPLs are for one-at-a-time expression evaluation. Playgrounds are way more sophisticated.

If you have Xcode, select File→New Playground and start playing around. There are many online tutorials out there. Definitely a better way to experiment, right?

Swift Playgrounds

Another way to learn!

Language Overview

Pssssssst

The Swift Tour is more encompassing than these notes and you might want to read through it first, or even instead of these notes.

But here goes yet another language overview anyway.

Declarations

Things you can declare in Swift include:

You can also declare by importing from a modules.

Examples are coming!

Types

Swift’s type system is super clean! Wildly, There are no “primitive types” and get this: there are no intrinsic built-in types! Every type—even the numeric types—are defined in the standard library! And this is cool: There are six kinds of types, and all types fit into one of those six kinds:

KindDescriptionValue or ReferenceExamples
Structure A basic value type, whose instances can have properties and methods Value



struct Point {
  var x: Double
  var y: Double
}
Class A basic reference type, whose instances can have properties and methods Reference class Point {
  var x: Double
  var y: Double
}
Enumeration Sum type Value enum Response {
  case ok
  case error(Int)
}
Tuple Product type Value
Function A type for functions, made from parameter types and a return type Reference (String, String) -> [Int]
Protocol Set of requirements that can be adopted by structs, classes, and enumerations. (Similar to what other languages call interfaces or mixins.) Instances are not allowed. N/A protocol Summarizable {
  var summary: String { get }
}
Wait, what? Numbers, booleans, and strings are structs?

Yes. Things are simpler that way. There is no need for primitive types and reference types like Java and JavaScript have. Simple values like 5, false, and "hello" can be the receivers of methods. No fancy wrapping is required.

Swift variables and constants are type-constrained. You are always permitted to specify the type in the declaration, but if you are initializing at the point of declaration, Swift will infer the type for you.

inference_example.swift
let dozen: Int = 12                     // Can specify type but don't have to
var fresh = true                        // Type is inferred to be Bool

var wins: Int                           // No value, type must be specified

let states = ["Ready", "Set", "Go"]     // Inferred to be [String]
let step = states.firstIndex(of: "Go")  // Inferred to be Int?

var counts: [String:Int] = [:]          // Empty collection MUST have type
var frequencies = [String:Int]()        // ...or use the type initializer

Value Types and Reference Types

Instances of value types are copied when passed around (assigned, passed as arguments, and returned from functions), while instances of reference types are shared.

value_vs_reference.swift
struct S { var x = 0 }  // structs have value semantics
var s1 = S()            // create an *instance* of type S
var s2 = s1             // copy, by value
s1.x = 5                // mutate through s1
assert(s2.x == 0)       // s2 unchanged

class C { var x = 0 }   // classes have reference semantics
var c1 = C()            // create an *instance* of type C
var c2 = c1             // reference copy
c1.x = 5                // mutate through c1
assert(c2.x == 5)       // c2 sees change
assert(c1 === c2)       // Same identity!

You should probably memorize the following:

Value TypesReference Types
Copied when assigned, passed, or returned Shared when assigned, passed, or returned
You will probably want to define your own == operator to compare the internal states Built-in === operator compares the references (identities)
Safe across multiple threads Used when you want shared, mutable state

For value types, let and var not only determine whether you can or cannot rebind a name, but also whether the associated instance is mutable or immutable. Woah!

> let combination = [31, 22, 0]

> combination = [3, 30, 11]
error: cannot assign to value: 'combination' is a 'let' constant

> combination[1] = 19
error: cannot assign through subscript: 'combination' is a 'let' constant
Exercise: Compare this with JavaScript. What does JavaScript do for const combination = [31, 22, 0]; combination = [3, 30, 11]; combination[1] = 19;?
Exercise: The following concepts are related, but not exactly the same:
  • let vs. var
  • immutable vs. mutable
  • value type vs. reference type
Spend some time discussing among friends how they differ.

Numbers and Booleans

Numbers are Booleans are not intrinsic to the language; they are just struct types in the Swift Standard Library. That’s rather remarkable, right? Check out this set of links to basic types in the Apple documentation. Also, take a look at the implementation of the Bool type at GitHub.

Here are some examples to get you started (and to practice with var and let):

  1> 90 >= 3 && !(3 < 5) || true
$R0: Bool = true
  2> false.toggle()
error: cannot use mutating member on immutable value: literals are not mutable
  2> let godotHasArrived = false
godotHasArrived: Bool = false
  3> godotHasArrived.toggle()
error: cannot use mutating member on immutable value: 'godotHasArrived' is a 'let' constant
  3> var dogIsBarking = true
dogIsBarking: Bool = true
  4> dogIsBarking.toggle()
  5> dogIsBarking
$R1: Bool = false

The numeric types in the standard library are:

TypeDescription
Int8-128 ... 127
Int16-32_768 ... 32_767
Int32-2_147_483_648 ... 2_147_483_647
Int64-9_223_372_036_854_775_808 ...9_223_372_036_854_775_807
IntEither Int32 or Int64, depending on hardware
UInt80 ... 255
UInt160 ... 65_536
UInt320 ... 4_294_967_296
UInt640 ... 18_446_744_073_709_551_616
UIntEither UInt32 or UInt64, depending on hardware
FloatIEEE-754 binary32
DoubleIEEE-754 binary64

Let’s see some integers in action:

int_examples.swift
// Binary, octal, and hex literals
assert(0b110100100 == 420)
assert(0o644 == 420)
assert(0x1a4 == 420)

// Some non-mutating methods
assert([5, 6, 7].contains(Int.random(in: 5..<8)))
assert([5, 6, 7].contains(Int.random(in: 5...8)))
assert(20.isMultiple(of: 5) == true)
assert(-23.signum() == -1)
assert(0.signum() == 0)
assert(23.signum() == 1)

// A fun one to be sure
assert(97.quotientAndRemainder(dividingBy: 25) == (quotient: 3, remainder: 22))
let (q, r) = 97.quotientAndRemainder(dividingBy: 25)
assert(q == 3 && r == 22)

// Some mutating methods
var x = 13
x.negate()
assert(x == -13)

// Generic numeric functions (not methods!)
assert(abs(-23) == 23)

// Static properties
assert(Int16.min == -32768)
assert(Int64.max == 9223372036854775807)
assert(UInt64.max == 18446744073709551615)
assert((UInt8.min, UInt8.max) == (0, 255))
assert((Int8.min, Int8.max) == (-128, 127))


And some doubles:

double_examples.swift
// Non-mutating operations on doubles
let nineFourths = 9.0 / 4
assert(nineFourths.squareRoot() == 1.5)
assert(nineFourths * 4 == 9)
assert(nineFourths.rounded() == 2)
assert(nineFourths.isEqual(to: 2.25))
assert(!nineFourths.isZero)
assert(nineFourths.isFinite)
assert(nineFourths.description == "2.25")

// Mutating operations on doubles
var x: Double = 529
x.formSquareRoot()                 // now x is 23
x.formRemainder(dividingBy: 10)    // now x is 3       
x.negate()                         // now x is -3
assert(x == -3)

// Static properties and methods
assert(Double.maximum(-5.5, 2) == 2)
assert(Double.minimum(-5.5, 2) == -5.5)
assert(Double.pi.isFinite)
assert(!Double.infinity.isFinite)
assert(Double.nan.isNaN)

// Remember your IEEE 754 Encoding?
assert(Double.greatestFiniteMagnitude.nextUp == Double.infinity)
assert(Double.leastNonzeroMagnitude.nextDown == 0.0)
assert(Double.leastNonzeroMagnitude.isSubnormal)
assert(8.0.isNormal)
Exercise: Is Swift’s % operator remainder or modulo? (Hint: try -19 % 7. Did you get -5 or 2? What does that mean?)

Characters and Strings

OMG SWIFT KNOWS ABOUT GRAPHEME CLUSTERS! When you ask Swift for the count of elements in the string, it counts what you see. Someone needs a big thank you for doing stuff right.

text_examples.swift
// Grapheme: x in a circle
// Characters: Lowecase x + Combining circle
let s3 = "x\u{20dd}"
assert(s3.count == 1)         // Yep, it’s a grapheme!
assert(s3.utf16.count == 2)   // 0078 20DD
assert(s3.utf8.count == 4)    // 78 E2 83 9D

// Grapheme: Lowecase e with acute accent
// Characters: Lowercase e + Combining acute
let s1 = "e\u{301}"
assert(s1.count == 1)         // Swift knows this a grapheme
let s2 = "\u{e9}"             // the precomposed character
assert(s1 == s2)              // == compares the graphemes, AMAZING!

// Grapheme: Woman surfer skin-tone 3
// Characters: Surfer + Skin Tone 3 + Joiner + Female Sign
let s4 = "🏄🏻‍♀"
assert(s4.count == 1)
assert(s4.unicodeScalars.count == 4)
assert(s4.utf16.count == 6)
assert(s4.utf8.count == 14)
let s5 = "🏄\u{1F3FB}\u{200D}♀"
assert(s4 == s5)

// Grapheme: Woman scientist
// Characters: Woman + Joiner + Microscope
let s6 = "👩\u{200d}🔬"
assert(s6.count == 1)
assert(s6.unicodeScalars.count == 3)
assert(s6.utf16.count == 5)
assert(Array(s6) == ["👩‍🔬"])
assert(Array(s6.utf16) == [55357, 56425, 8205, 55357, 56620])
assert(Array(s6.utf8)
    .map { String($0, radix:16) }
    .joined(separator: " ") ==
    "f0 9f 91 a9 e2 80 8d f0 9f 94 ac")

Apple says “Strings in Swift are Unicode correct and locale insensitive, and are designed to be efficient.” We’ll see the efficiency part later. But now, how about some more String things?

more_text_examples.swift
assert(String(repeating: "yes", count: 3) == "yesyesyes")

let poem = """
    Today is
    a good
    day ☀️
    """

assert(poem.hasPrefix("Today is\na good"))
assert(poem.hasSuffix("ood\nday ☀️"))
assert(poem.contains("o"))
assert(!poem.contains("O"))
assert(!poem.isEmpty)
assert(poem.uppercased() == "TODAY IS\nA GOOD\nDAY ☀️")
assert(poem < "Tomorrow")

assert(String(127, radix: 16) == "7f")
assert(String(127, radix: 16, uppercase: true) == "7F")

var s = "hello"
s.append("world")
assert(s == "helloworld")
assert(s + " people" == "helloworld people")
s += "!!!"
assert(s == "helloworld!!!")

s = "😂😎🤔🎃"
s.removeFirst()
assert(s == "😎🤔🎃")

Text processing is a huge topic! Check out the Apple Standard Library Documentation for the String type and the Character type.

Arrays, Sets, and Dictionaries

These are all generic value types (structs) in the standard library. Take advantage of shorthand notation: write Array<T> as [T] and write Dictionary<K,V> as [K:V]. Some array examples (no substitute for a full dive into the documentation, of course):

array_examples.swift
var a = [10, 20, 30]
var b = [Int](repeating: 1, count: 4)
a[2] = 40
a.append(50)
a += b
assert(a.count == 8)
a[1...3] = [0, 2, 4]                 // Subscripts can be ranges!
a.remove(at: 3)
assert(a == [10, 0, 2, 1, 1, 1, 1])
assert(a[2..<5] == [2, 1, 1])        // Half-open range
assert(a[1..4] == [0, 2, 1, 1])      // Closed range
assert(a[5...] == [1, 1])            // Partial range from
assert(a[...3] == [10, 0, 2, 1])     // Partial range through
assert(a[..<3] == [10, 0, 2, 1])     // Partial range upto

Sets need a little more syntax. Set literals look like array literals, and Swift favors arrays:

set_examples.swift
let a: Set<Int> = [10, 20, 30]
let b: Set<Int> = [20, 30, 40]
assert(a.union(b).count == 4)
assert(a.intersection(b) == Set([20, 30]))
assert(a.contains(10))
assert(!b.isEmpty)

Here’s some dictionary fun:

dictionary_examples.swift
let capitals = [
    "Netherlands": "Amsterdam",
    "Aruba": "Oranjestad",
    "Cura\u{e7}ao": "Willemstad",
    "Sint Maarten": "Philipsburg",
]

assert(capitals.count == 4)
assert(capitals.keys.contains("Aruba"))
assert(capitals.values.sorted().last == "Willemstad")

for (country, capital) in capitals {
    print("The capital of \(country) is \(capital).")
}

Optionals

Yay, no billion dollar mistake in Swift. Sure, there is a value called nil, but it does not belong to the type String. It belongs to the type String? though. This is nice: if there’s a chance you might need nil, you need a different type.

Think of an optional as a box. A value of Int? is either a box with an Int in it, or an empty box:

 
5
Optional.none
Optional.some(5)
optional_demo.swift
// Q: How do I specify the empty box? A: nil
let firstGreatGrandchild: String? = nil

// Q: How do put something in a box (i.e., WRAP the optional)?
// A: Just assign, Swift will know what to do
let supervisor: String? = "Alice"

// Q: How do I unwrap? A: There are many ways, but the best is to
// write code to handle *both* the something-is-there case and the
// something-is-not-there case. Take advantage of Swift allowing
// bindings in its compound statements (if, guard). if-let is ugly
// because error handling is at the end of fun and there’s too much
// nesting
for s in ["42", "dog", "3xy", "-2E3", "-1"] {
    if let i = Int(s) {
        print("\"\(s)\" parsed as an integer is \(i)")
        print("I can do actual work now")
        print("Kind of ugly I'm so nested")
    } else {
        print("\"\(s)\" can't be parsed to an integer")
    }
}

// guard-else better!! Error handled immediately; less nesting, awesome!
for s in ["42", "dog", "3xy", "-2E3", "-1"] {
    guard let i = Int(s) else {
        print("\"\(s)\" can't be parsed to an integer")
        continue
    }
    print("\"\(s)\" parsed as an integer is \(i)")
    print("I can do actual work now")
    print("All the work is in the happy path of the func")
    print("Not nested how pretty")
}

// Q: But what if I just want to unwrap and I have a default value?
// A: You are in luck! There is the Nil Coalescing operator (??).
var frequencies = ["A": 50, "B": 22]
frequencies["A"] = (frequencies["A"] ?? 0) + 1
frequencies["C"] = (frequencies["C"] ?? 0) + 1
assert(frequencies == ["A": 51, "B": 22, "C": 1])

// Q: Can I chain optionals? A: Yes, the syntax is very similar to
// JavaScript's (just slightly different when subscripting and 
// invocation)
assert(frequencies["A"]?.description == "51")
assert(frequencies["X"]?.description == nil)

// Q: Can I just unwrap the darn thing and not have to worry about
// if-let, guard-let, and all this syntax. Just let me unwrap! I know
// what the heck I am doing. A: Okay, you can, but if your optional
// is nil, your program will crash hard. No backsies!
let message: String? = "I got this"
print(message!)

Functions

As is typical for a modern, statically typed language, function declarations need types to be specified for parameters and return values. But here’s something pretty cool: every parameter has BOTH a parameter name and an argument label:

simple_functions.swift
// By default, parameter name and argument label are the same
func perimeter(base: Int, height: Int) -> Int {
    return 2 * (base + height)
}

// We can specify both the parameter name and argument label
func average(of x: Int, and y: Int) -> Double {
    return Double(x + y) / 2.0
}

// Underscore means no argument label may appear in call
func middle(_ x: Int, _ y: Int) -> Double {
    return Double(x + y) / 2.0
}

assert(perimeter(base: 4, height: 20) == 48)
assert(average(of: 5, and: 8) == 6.5)
assert(middle(5, 8) == 6.5)
Exercise: In other languages, using argument labels means you can pass arguments without worrying about their order. Is this the case in Swift? Try it!

The functions above appear in documentation as perimeter(base:height:), average(of:and:), and middle(_:_:).

Yes, Swift has default parameter values, variadic parameters, and inout parameters:

more_functions.swift
func moveUp(_ level: inout Int, by steps: Int = 1) {
    level += steps
}

var level = 10
moveUp(&level)
assert(level == 11)
moveUp(&level, by: 8)
assert(level == 19)

func zeros(_ items: Int...) -> Int {
    return items.filter{ $0 == 0 }.count
}

assert(zeros() == 0)
assert(zeros(2, 3) == 0)
assert(zeros(2, 0, 5, 0, 0, 0, 1, 0) == 5)

What’s the deal with inout? Parameters cannot be changed inside the function unless you prefix their type with inout. This modifies that value of the corresponding argument (woah). Swift requires that inout parameters:

Closures

Hopefully, you won’t get confused with this, but here goes:

However, most of the time we use the term “closure” in Swift, we refer to a closure expression. There are two forms:

If the final parameter of a function is a closure, you get a very convenient syntax, writing the closure after the argument list.

closure_demo.swift
// If the last parameter is a closure
func addThenOperate(_ x: Int, _ y: Int, then f: (Int) -> Int) -> Int {
    return f(x + y)
}

// You can call it normally or use a shorthand syntax
assert(addThenOperate(5, 3, then: { $0 * $0 }) == 64)
assert(addThenOperate(5, 3) { $0 * $0 } == 64)

// The shorthand syntax is very common in pipelines
func sumOfCubesOfOdds(from a: [Int]) -> Int {
  return a.filter{ $0 % 2 != 0 }.map{ $0 * $0 * $0 }.reduce(0, +)
}

assert(sumOfCubesOfOdds(from: [8, 3, 1, 8, -1]) == 27)

Structures and Classes

Structures and classes are types from which you create instances. These types provide:

Structs are the simplest. Here is a very simple struct, illustrating how properties can be either stored properties or computed properties.

circle.swift
import Foundation                         // for sqrt

let π = Double.pi

struct Circle {
    var radius: Double                    // stored property
    var area: Double {                    // computed property
        get {return π * radius * radius}
        set(a) {radius = sqrt(a/π)}
    }
}

var c = Circle(radius: 10)
assert(c.radius == 10 && c.area == 100*π) // invokes get
c.area = 16*π                             // invokes set
assert(c.radius == 4 && c.area == 16*π)

This struct had properties only. We got an initializer for free though, because structs give you one that take all the stored properties in order. Here’s one with an explicit initializer, a subscript, and a few methods, even mutating methods (you know what those are, right?).

triangle.swift
import Foundation

struct Triangle: CustomStringConvertible {
    static let sides = 3

    var vertices: [(Double, Double)]

    init(v1: (Double, Double), v2: (Double, Double), v3: (Double, Double)) {
        vertices = [v1, v2, v3]
    }

    subscript(index: Int) -> (Double, Double) {
        get { return vertices[index] }
        set { vertices[index] = newValue }
    }

    func perimeter() -> Double {
        let ((x1, y1), (x2, y2), (x3, y3)) = (vertices[0], vertices[1], vertices[2])
        return hypot(x1-x2, y1-y2) + hypot(x2-x3, y2-y3) + hypot(x3-x1, y3-y1)
    }

    func area() -> Double {
        let ((x1, y1), (x2, y2), (x3, y3)) = (vertices[0], vertices[1], vertices[2])
        return abs((x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2)) / 2)
    }

    mutating func translateBy(dx: Double, dy: Double) {
        self.vertices = self.vertices.map{ (x, y) in (x + dx, y + dy) }
    }

    mutating func scaleBy(sx: Double, sy: Double) {
        self.vertices = self.vertices.map{ (x, y) in (x * sx, y * sy) }
    }

    mutating func rotate(by θ: Double) {
        self.vertices = self.vertices.map{ (x, y) in (x*cos(θ)-y*sin(θ), x*sin(θ)+y*cos(θ)) }
    }

    var description: String {
        return "<Triangle {\(self.vertices.map({ "\($0)" }).joined(separator: "-"))}>"
    }
}

var t = Triangle(v1: (0, 0), v2: (3, 0), v3: (0, 4))
t.translateBy(dx: 10, dy: -3)
assert(t.description == "<Triangle {(10.0, -3.0)-(13.0, -3.0)-(10.0, 1.0)}>")
assert("*\(t)*" == "*<Triangle {(10.0, -3.0)-(13.0, -3.0)-(10.0, 1.0)}>*")
assert(t.perimeter() == 12)
assert(t.area() == 6)
t.rotate(by: 3)
t.scaleBy(sx: 5, sy: 5)
assert(abs(t.area() - 150) < 1e-10)
assert(abs(t.perimeter() - 60) < 1e-10)
assert(Triangle.sides == 3)

We also illustrated a type property. In Swift we have instance properties and type properties, and we have instance methods and type methods.

Oh, CustomStringConvertible is a protocol; we’ll see those later.

Let’s talk about classes now. Structures are value types but classes are reference types. Classes also have more functionality than structures: classes can have deinitializers, they can inherit from superclasses, and they have run-time type checking and casting. That’s quite a bit more! Here’s a class that’s unfortunately a bit too contrived. There are better examples online. Somewhere.

rectangles.swift
// First, a superclass. Properties are defined with let, so they
// can't be changed after they are set by an initialzer.

class Rectangle {
    let width: Double
    let height: Double

    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    var description: String {
        return "Rectangle \(self.width) x \(self.height)"
    }

    func area() -> Double {
        return self.width * self.height
    }
}

class Square: Rectangle {
    init(side: Double) {
        super.init(width: side, height: side)
    }

    convenience init() {
        self.init(side: 1.0)
    }

    override var description: String {
        return "Square with side \(self.width)"
    }
}

// You can create instances as usual
let r = Rectangle(width: 20, height: 30)
assert(r.width == 20)
assert(r.area() == 600)
assert(r.description == "Rectangle 20.0 x 30.0")
let s = Square(side: 50)
assert(s.width == 50)
assert(s.area() == 2500)
assert(s.description == "Square with side 50.0")

// Now since squares are rectangles, you can do this:
let rekt = Square(side: 2.18281828)
let rekts = [r, s, rekt, Rectangle(width: 3, height: 5)]

// You can type check
assert(!(rekts[0] is Square))
assert(rekts[1] is Square)

We’ll discuss deinitializers and casting later.

Enums

Swift has a pretty decent disjoint sum type, yay. Simple example first:

token.swift
enum Token {
    case plus
    case minus
    case number(Double)
    case identifier(String)

    func lexeme() -> String {
        switch self {
            case .plus: return "+"
            case .minus: return "-"
            case .number(let value): return String(value)
            case .identifier(let name): return name
        }
    }
}

let expression: [Token] = [.number(4.0), .plus, .identifier("x")]
let text = expression.map{ $0.lexeme() }.joined(separator: " ")
assert(text == "4.0 + x")

Values of an enumeration type may be assigned raw values, from the enumeration’s underlying raw type. This allows additional information to be associated with each enum value.

raw_values.swift
enum Suit: String {
    case spades, hearts, diamonds, clubs
}

enum Direction: Int {
    case north = 0, east = 90, south = 180, west = 270
}

assert(Suit.hearts.rawValue == "hearts")
assert(Direction.south.rawValue == 180)
assert(Direction.west == Direction(rawValue: 270))

Recursive enumerations need to be marked with indirect:

binary_tree.swift
indirect enum Tree<T> {
    case empty
    case node(Tree, T, Tree)

    var size: Int {
        switch self {
            case .empty: return 0
            case .node(let left, _, let right):
                return left.size + 1 + right.size
        }
    }
}

let t: Tree<Int> = .node(.node(.empty, 3, .empty), 7, .empty)
assert(t.size == 2)

Hey did we just make a generic type? We did! Hang on for more details. Coming soon.

Extensions

Do you want to add properties or initializers or methods or subscripts to a struct or class or protocol “later”? YOU CAN DO THIS!

extension_examples.swift
extension Int {
    var abs: Int {return self >= 0 ? self : -self}

    func factorial() -> Int {
        return (1...self).reduce(1, {$0 * $1})
    }

    static var allOnes: Int {return ~0}
}

assert(8.abs == 8)
assert(Int.allOnes == -1)
assert(10.factorial() == 3628800)
Exercise: Make an extension for the Binary Tree enum above, with properties and methods for node insertion, deletion, preorder traversal, inorder traversal, postorder traversal, height, etc.

Operators

Standard Operators

The built-in operators are defined in the standard library. The unary operators are:

OperatorFixityDescription
+PrefixUnary plus
-PrefixUnary negation
!PrefixLogical NOT
~PrefixBitwise NOT
..<PrefixPartial Range UpTo
...PrefixPartial Range Through
...PostfixPartial Range From

The binary (and ternary) operators in Swift are all infix, and from highest to lowest precedence, are:

GroupOperatorDescriptionAssociativity
BitwiseShiftPrecedence <<   >> Bitwise left shift, Bitwise right shift None
MultiplicationPrecedence *   /   %   &*   & Multiplication, Division, Remainder, Multiplication ignoring overflow, Bitwise AND Left
AdditionPrecedence +   -   &+   &-   |   ^ Addition, Subtraction, Modular addition, Modular subtraction, Bitwise OR, Bitwise XOR Left
RangeFormationPrecedence ..<   ... Half-open range, Closed range None
CastingPrecedence is   as   as?   as! Type check, Type cast, Type cast returning optional, Type cast that can crash Left
NilCoalescingPrecedence ?? Nil Coalesce Right
ComparisonPrecedence <   <=   >   >=   ==   !=   ===   !==   ~=   .=   .!=   .<   .<=   .>   .>= Less, Less or equal, Greater, Greater or equal, Equal, Not equal, Identical, Not identical, Matches, Pointwise equal, Pointwise not equal, Pointwise less, Pointwise less or equal, Pointwise greater, Pointwise greater or equal None
LogicalConjunctionPrecedence && Logical AND Left
LogicalDisjunctionPrecedence || Logical OR Left
DefaultPrecedence Any custom binary operator you define yourself without specifying a precedence level None
TernaryPrecedence ?: Ternary conditional Right
AssignmentPrecedence =   *=   /=   %=   +=   -=   &=   |=   ^=   <<=   >>= Assign Right

Overloading the standard operators

An example is the best way to see this:

vectors.swift
import Foundation

struct Vector: CustomStringConvertible {
    let i: Double
    let j: Double

    func magnititude() -> Double {
        return sqrt(i * i + j * j)
    }

    var description: String {
        return "<\(i),\(j)>"
    }
}

func + (left: Vector, right: Vector) -> Vector {
    return Vector(i: left.i + right.i, j: left.j + right.j)
}

func * (left: Vector, right: Vector) -> Double {
    return left.i * right.i + left.j * right.j
}

prefix func - (v: Vector) -> Vector {
    return Vector(i: -v.i, j: -v.j)
}

let u = Vector(i: 3, j: 4)
let v = Vector(i: -5, j: 10)
assert(u.i == 3)
assert(u.j == 4)
assert(u.magnititude() == 5)
assert((u + v).description == "<-2.0,14.0>")
assert(u * v == 25)
assert((-v).description == "<5.0,-10.0>")

Creating custom operators

If you want to declare your own operators, you can! For infix operators, you can use an existing precedence group, create your own precedence group, or use the default one; you can define your own associativity (left, right, or none); the default is none.

custom_operator_example.swift
precedencegroup JustForFun {
  higherThan: AdditionPrecedence
  lowerThan: MultiplicationPrecedence
  associativity: left
}

infix operator ~|*|~ : JustForFun
postfix operator ^^

func ~|*|~ (x: Int, y: Int) -> Int {
    return 2 * x + y
}

postfix func ^^ (x: Int) -> Int {
    return x - 2
}

assert(8^^ ~|*|~ 3 == 15)

Protocols

Time for the important stuff! Apple says Swift is the world’s first protocol oriented language. So what is a protocol? See what Apple has to say about them in the language guide. Maybe go through this tutorial. Ok TL;DR the language guide says “a protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements.” Super simple examples to start:

protocol_examples.swift
import Foundation

protocol Summarizable {
    var summary: String { get }
}

struct Circle: Summarizable {
    var radius = 1.0
    var summary: String { return "Circle with radius \(radius)" }
}

enum Direction: Int, Summarizable {
    case north, east, south, west
    var summary: String { return "Bearing \(90 * self.rawValue)" }
}

let a: [Summarizable] = [Circle(radius: 5), Direction.west]
assert(a[0].summary == "Circle with radius 5.0")
assert(a[1].summary == "Bearing 270")

Protocols are everywhere in Swift. They are even used in cases where you’d think classes and subclasses would be used. Apple has this to say:

Use Structures and Protocols to Model Inheritance and Share Behavior Structures and classes both support a form of inheritance.

Structures and protocols can only adopt protocols; they can't inherit from classes. However, the kinds of inheritance hierarchies you can build with class inheritance can be also modeled using protocol inheritance and structures.

If you're building an inheritance relationship from scratch, prefer protocol inheritance. Protocols permit classes, structures, and enumerations to participate in inheritance, while class inheritance is only compatible with other classes. When you're choosing how to model your data, try building the hierarchy of data types using protocol inheritance first, then adopt those protocols in your structures.

Oh and by the way, you can extend protocols too.

CLASSWORK
Let’s do a code-along! We’ll do the classic OOP example with shapes that can rectangles or circles, but instead of an abstract base class Shape, we’ll make it a protocol, and we’ll make circles and rectangles structs instead of classes. Fun!

Generics

Since Swift is a statically typed language, you’d probably cry if you only had a single array type, a single set type, and a single array type, with no way to type-constrain the elements they could hold. You know you can constrain the elements, but what’s the theory behind it all?

When creating a generic type or function, you supply type parameters. The generic type or function is not a real type or function, but specific instantiations are. So in this script:

stack.swift
class Stack<T> {
    private var data: [T] = []
    func push(_ item: T) { data.append(item) }
    func pop() -> T? { return data.popLast() }
    var count: Int { return data.count }
}

var numbers = Stack<Int>()
var names = Stack<String>()

numbers.push(30)
names.push("Hello")
print(names.pop() ?? "oops")
assert(names.count == 0)

Stack is not a real class (it’s more like a template for a class), but Stack<Int> and Stack<String> are. Similarly, in:

twice.swift
func twice<T>(_ f: (T) -> T, on x: T) -> T {
    return f(f(x))
}

assert(twice({$0 * 5}, on: 10) == 250)
assert(twice({(x, y) in (x + 1, x * y)}, on: (2.5, 10.0)) == (4.5, 87.5))

twice is not a real function (it’s more like a template for a function), but twice<Int> and twice<(Double, Double)> are.

What do we mean by real class?

The Swift compiler will actually compile generic instantiations of the same template separately. So a generic type of function never results in compiled code itself; only its instantiations do.
Exercise: Why?

You can require (constrain) generic type parameters so that any instantiating type argument must conform to a specific protocol (or protocols, believe it or not, see the docs for how). Here’s a trivial example:

compare.swift
func comparison_results<T: Comparable>(_ x: T, _ y: T) -> (T, T) {
    return (min: x < y ? x : y, max: x > y ? x : y)
}

assert(comparison_results(55, 13) == (min: 13, max: 55))
Exercise: Compile this code without the : Comparable constraint and make note of the error. Also try invoking the function with two dictionaries, and make note of the error.

Errors and Error Handling

It seems like the bad or unexpected things that happen fall into three categories:

Error Recovery

When designing for fault-tolerance, design your operations so that they succeed or fail. You have two techniques in Swift to build such operations:

An error instance is an instance of any type conforming to the protocol Error. Here’s a long example:

song_errors.swift
import Foundation

struct Song { 
    let songId: String
    let lyrics: String
    let price: Int
}

struct User {
    let userId: String
    var name: String
    var currency: Int
}

let songs = [
    "1": Song(songId: "1", lyrics: "Lalala", price: 10),
    "2": Song(songId: "2", lyrics: "Dumdumdum", price: 50),
]

let validTokens = [
    "ABC": User(userId: "alice", name: "Alice", currency: 30),
    "XYZ": User(userId: "bob", name: "Bob", currency: 15),
]

enum SongFetchError: Error {
    case illegalToken
    case noSuchSong
    case insufficientCurrency(shortBy: Int)
}

/*
 * Example of a fetch that throws an error on failure.
 */
func retrieveSong(token: String, songId: String) throws -> Song {
    guard var user = validTokens[token] else {
        throw SongFetchError.illegalToken
    }
    guard let song = songs[songId] else {
        throw SongFetchError.noSuchSong
    }
    guard user.currency >= song.price else {
        throw SongFetchError.insufficientCurrency(shortBy: song.price - user.currency)
    }
    user.currency -= song.price
    return song
}

/*
 * Example of a fetch that returns a Result instance.
 */
func getSong(token: String, songId: String) -> Result<Song, SongFetchError> {
    guard var user = validTokens[token] else {
        return .failure(.illegalToken)
    }
    guard let song = songs[songId] else {
        return .failure(.noSuchSong)
    }
    guard user.currency >= song.price else {
        return .failure(.insufficientCurrency(shortBy: song.price - user.currency))
    }
    user.currency -= song.price
    return .success(song)
}

// ---------------------------------------------------------------------------------------
// HANDLING ERRORS THAT ARE THROWN
// ---------------------------------------------------------------------------------------

// General way: do-catch statement with try
do {
    let song = try retrieveSong(token: "ABC", songId: "2")
    print("Sing along: \(song.lyrics)")
} catch SongFetchError.illegalToken {
    print("Sorry, bad token")
} catch SongFetchError.noSuchSong {
    print("Sorry, that's not there")
} catch SongFetchError.insufficientCurrency(let need) {
    print("You need \(need) more currency units")
} catch {
    print("I don't know what's going on")
}

// If you don't care about the error, you can make it an optional with "try?"
let lyrics = (try? retrieveSong(token: "ABC", songId: "2"))?.lyrics ?? "Nothing to sing"
print(lyrics)

// If you KNOW IT WILL WORK you can force it with "try!" but beware
let song = try! retrieveSong(token: "XYZ", songId: "1")
print(song.lyrics)

// ---------------------------------------------------------------------------------------
// HANDLING ERRORS IN RESULT INSTANCES
// ---------------------------------------------------------------------------------------

// General way: switch on .success and .failure
switch getSong(token: "ABC", songId: "2") {
case .success (let song):
    print("Sing along: \(song.lyrics)")
case .failure(SongFetchError.illegalToken):
    print("Sorry, bad token")
case .failure(SongFetchError.noSuchSong):
    print("Sorry, that's not there")
case .failure(SongFetchError.insufficientCurrency(let need)):
    print("You need \(need) more currency units")
}

// You can ignore failure by just binding to .success
if case .success = getSong(token: "XYZ", songId: "1") {
    print("Sing along: \(song.lyrics)")
}

// Invoking .get() on a result makes it throw on error, so
//   try result.get() can be used in a do-catch
//   try? result.get() ---> makes an optional
//   try! result.get() ---> will force 

When would you return a Result instance rather than throwing? Well one case is with async operations (not discussed on this page). Because if you throw from an async operation, your caller can’t catch it. But you can pass a result object to a callback, see? (Here’s an article.)

Runtime Errors (Crashes)

Now what about things that are just so bad that when they happen, your Swift program crashes hard? These runtime errors are not thrown and they can’t be caught. They are fatal. Your program just dies when they happen. Here’s an incomplete list of when they can occur, but it’s a start:

Careful with !, it’s an exclamation mark for a reason!

Patterns

Like many modern languages, you aren’t limited to assigning to a single variable; you can bind using a pattern. And patterns can appear in lots of places, such as while, if, and guard conditions, and of course, in variable and constant declarations, the index of a for-statement, the cases of a switch statement, the catch clauses of a do-statement. There are several kinds of patterns, with various restrictions. We’ll lay them all out in code, with examples:

pattern_examples.swift
// The WILDCARD PATTERN
for _ in 1...5 { print("Hey!") }

// The IDENTIFIER PATTERN
for number in 1...5 { print(number) }

// The TUPLE PATTERN - matches only
switch ("dog", 5) {
    case ("cat", 10): assert(false)
    case ("dog", 15 - 10): assert(true)
    case ("dog", 2): assert(false)
    default: assert(false)
}

// The VALUE BINDING PATTERN - matches and binds
var point = (1, 2, 3)
var matched = false
if case (let x, _, let z) = point, x < 10 {
    assert(x == 1 && z == 3)
    matched = true
}
assert(matched)

// The ENUMERATION CASE PATTERN
// Matches any case of an enumeration
// Cam only appear in
//     (1) switch case labels
//     (2) while, if, guard conditions
//     (3) for-in case
enum Token { case print; case eq; case num(Int); case id(String)}
let program: [Token] = [.id("x"), .eq, .num(3), .print, .num(100)]
var square = Double.nan
if case let .num(number) = program[2] {
    square = Double(number * number)
}
assert(square == 9)

// The OPTIONAL PATTERN
// Matches the values in a .some part of an optional
// Just syntactic sugar for an Enumeration Case Pattern for Optionals
// So can only appear where Enumeration Case Patterns can appear
var todos: [String] = []
let advice: String? = "Breathe deeply"
if case let todo? = advice {
    todos.append(todo)
}
assert(todos == ["Breathe deeply"])

// The TYPE CHECK ("is") PATTERN
// Can ONLY appear in switch-statement case labels
class Animal {}
class Dog: Animal {}
let pet: Animal = Dog()
switch pet {
    case is Dog: assert(true)
    default: assert(false)
}

// The TYPE CASTING ("as") PATTERN
let things: [Any] = [1, "dog", false]
switch things[1] {
    case let n as Int: print("Ima number whose square is \(n * n)")
    case let s as String: print(s)
    default: print("I don't even know what's going on")
}

// The EXPRESSION PATTERN
// Can ONLY appear in switch-statement case labels
switch Int.random(in: -15...100) {
    case 0: print("Zero")
    case let n where n.isMultiple(of: 10): print("Multiple of 10")
    case ...0: print("Negative")
    case 2: print("A couple")
    case ...5: print("Not many")
    default: print("Quite a few")
}

For more on patterns, read this extensive 4-part article on patterns, and of course, the section on patterns in the Swift book.

Statements

We are now ready to give a complete overview of the Swift statements. Swift statements are way more powerful and flexible than you might be used to in C: breaks and continues can be labeled, and there are ample cases for pattern-matching and destructuring, even directly in for-, if-, and while- statements. There are lovely guard and defer statements. Here is how they’re laid out:

  Stmt
      ExpStmt
      DeclStmt
      LoopStmt
          ForInStmt
          WhileStmt
          RepeatWhileStmt
      BranchStmt
          IfStmt
          GuardStmt
          SwitchStmt
      ControlTransferStmt
          BreakStmt
          ContinueStmt
          FallthroughStmt
          ReturnStmt
          ThrowStmt
      DeferStmt
      DoStmt
      LabeledStmt
      CompilerControlStmt
          ConditionalCompilationStmt
          LineControlStmt
          DiagnosticStmt

The syntax for a few of these follows:

ExpStmt
DeclStmt
ForInStmt
WhileStmt
RepeatWhileStmt
IfStmt
GuardStmt
SwitchStmt
BreakStmt
ContinueStmt
FallthroughStmt
ReturnStmt
ThrowStmt
DeferStmt
DoStmt
LabeledStmt

Many of these are familiar to C programmers, but there are a few new statements and some superpowers, like pattern matching on tuples and enums and optionals in compound statements and conditionals. The switch is more modern: you have to opt-in to fallthrough. And remember, catching errors is much cleaner, and the defer statement makes for guaranteed cleanup. Speaking of conditions, here’s the syntax:

Condition

The let and var bindings in a conditional are only for optionals, and in fact, they're really just syntactic sugar:

condition_examples.swift
// The only two conditionals we need are the plain expression and the
// *case condition*, namely "case Pattern = Exp". One of the patterns
// is called an enum-case-pattern, and Optionals are enums with .some
// and .none so we can unwrap optionals with normal pattern matching:
if case .some(let x) = Int("100") {
    print(x * 3)
}

// The condition form "(let|var) pattern = Exp" is ONLY allowed to have
// an initializing expression that is an optional. So the following is
// really just syntactic sugar for the statement above:
if let x = Int("100") {
    print(x * 3)
}
Why are patterns in conditions so awesome?

Patterns often introduce bindings, and these new names are local to the construct in which they are defined. There’s no need to make a non-local variable just to use it in both the if or while condition and the statement body, as is required in many other languages.

Let’s see some statements in action:

statement_examples.swift
let points = [(3, 8), (2, 5), (13, 3), (1, 21)]
enum Token { case print; case eq; case num(Int); case id(String)}
let program: [Token] = [.id("x"), .eq, .num(3), .print, .num(100)]

// For-statement with pattern and where
for (x, y) in points where y >= 8 {
    print(x, y)
}

// For-statement with case
for case let Token.num(number) in program {
    print(number * number)
}

// Repeat-while statement
var level = 0
repeat {
    level += Int.random(in: -3...10)
    print("Now at level \(level)")
} while level < 90

// Multiple conditions in an if-statement
if case let (x, y) = points[2], x * y > 20 {
    print("Vertically \(y) and horizontally \(x)")
} 

// Defer statement: runs just before the function returns, whether
// normally or because of a throw!
struct BoxError: Error { }

class Box {
    private(set) var isOpen = false
    func open() { print("opening"); isOpen = true }
    func close() { print("closing"); isOpen = false }
    func tinker() throws { throw BoxError() }
}

func doWork(_ box: inout Box) throws {
    defer { box.close() }
    box.open()
    try box.tinker()
}

var myBox = Box()
try? doWork(&myBox)
assert(!myBox.isOpen)

Pragmatics

Safety Features

A safe language provides lots of guarantees about program behavior and has as few ambiguities as possible. It provides features to help the programmer write correct code.

In Swift:

Guaranteed Initialization

Not only does Swift give a compile-time error when trying to use an identifier that has never been declared, it will give a compile-time error when using an identifier that has not been initialized.

var message: String
print(message)        // compile time error!

The compiler does control-flow analysis to make sure all uses of identifiers are on a path through the code where the identifier has been initialized, i.e., Swift is a definitive initialization language, unlike say Go, which provides (default implicit initialization).

Overflow Checking

The following blocks of code cause fatal, non-recoverable, crashes:

let lights = ["red", "amber", "green"]
print(lights[200])      // CRASH
var x: Int8 = 100
var y: Int8 = 100
print(x + y)            // CRASH

We can’t do much about the bounds checking other than to be careful, but if you want “wraparound” semantics for your arithmetic operators, use &+ and friends:

var x: Int8 = 100
var y: Int8 = 100
assert(x &+ y == -56)

let sheep = (Array(0...5) as [Int16]).map({delta in 32765 &+ delta})
assert(sheep == [32765, 32766, 32767, -32768, -32767, -32766])

Relevant XKCD:

Copy-on-Write

Pause and think. Structs, arrays, and dictionaries are all value types, meaning that they are copied when assigned or passed as arguments, so if they are large, how can copying possibly be efficient?

In practice, we rarely modify the copy of a large structure (in fact, the larger the structure, the more likely we are to use an inout modifier and actually mutate it). But in the case where we do want to make a copy, we’re golden: Swift only physically makes a copy just before the first write. Prior to the first write (if any), large structures are indeed shared behind the scenes.

copy-on-write.png

Automatic Reference Counting

What happens to instances you create that are no longer accessible by any reference?

In most languages, a tracing garbage collector runs on a separate thread, preempting your program to look for and remove inaccessible instances. This is an expensive operation! And would you want to interrupt flight control software or medical software?

Swift doesn’t do tracing garbage collection, so that its performance characteristics are predictable. Instead, it uses automatic reference counting. Each instance has a reference count, starting at 0. Every time a reference is created for it, the count increases; when a reference goes away, the count decreases. When the count goes to zero, the instance is deallocated (and the deinitializer is called). Example:

arc_example.swift
var history: [String] = []

class C {
    init() {history.append("init")}
    deinit {history.append("deinit")}
}

func f(_ c: C) {             // 3. count==2
    history.append("f")
    print(c)
    history.append("/f")
}                            // 4. c out of scope, count == 1

func main() {
    history.append("main")
    let x = C()              // 1. object created, count==1
    f(x)                     // 2. reference passed
    history.append("/main")
}                            // 5. x out of scope, count == 0

main()
assert(history == ["main", "init", "f", "/f", "/main", "deinit"])

In exchange for predictability, Swift requires you to understand how references are counted so that you do not end up with memory leaks. Sometimes you need weak references. Objects are only deallocated when their strong reference count goes to zero.

weak_references.swift
class Person {
    var name: String
    var boss: Person?
    weak var assistant: Person?

    init(name: String, boss: Person? = nil) {
        initCount += 1
        self.name = name
        self.boss = boss
    }
    deinit {
        deinitCount += 1
    }
}

func main() {
    let alice = Person(name: "Alice")
    let bob = Person(name: "Bob", boss: alice)
    alice.assistant = bob
}

var (initCount, deinitCount) = (0, 0)
main()
assert(initCount == 2 && deinitCount == 2)
CLASSWORK
We’re going to over this example, with drawings and animations. We’ll see the memory leak if both references were strong, and show how deallocation works beautifully when one of the references is weak.

Swift in Practice

Library Ecosystem

Unit Testing

Swift UI

Deploying Apps

Things We Did Not Cover

We missed quite a lot, including, but not limited to:

Summary

We’ve covered:

  • ...