Busy Developer's Guide to Swift

ted.neward@newardassociates.com | Blog: http://blogs.newardassociates.com | Github: tedneward | LinkedIn: tedneward

Beforew we begin

Quick note:

Objectives

Get started with the Swift programming language

NOT an intro to iOS or OSX programming/APIs

Swift Overview

What is this thing?

Swift Overview

What is Swift?

Swift Overview

Basic C-family language

Swift Overview

Object-oriented

Swift Overview

(Limited) Functional Language

Swift Overview

Swift Language Documentation

Installing Swift

Hello, XCode

Installing Swift

Installing Swift means installing XCode

Installing Swift

Note: XCode will require enabling "Developer Mode" on your Mac

Note: XCode will also require sudo/root access for certain things

Installing Swift

Once installed, XCode can compile Swift code

... or run from the command-line

Installing Swift

As of Swift 3.0, available on Linux

Basics

First steps

Basics

Beginnings

Basics

Hello, Swift

import Foundation

print("Hello, World!")

Basics

Comments

Basics

Semicolons/Terminators

Basics

Variable types

Basics

Primitive types support most of the C-family operators

Basics

Variable declaration

Basics

Locals

let money = 42000
// let moneyD : Double = money // Illegal
let moneyD2 : Double = Double(money)
    
var moneyD = 42000
moneyD = 50000

Basics

Strings can be interpolated

Basics

String interpolation

let age = 42
let message = "He is \(age) years old"

Control Flow

Left, right or center?

Control Flow

Control Flow: If

Control Flow

If

var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
  print("It's very cold. Consider wearing a scarf.")
}
else if temperatureInFahrenheit > 80 {
  print("Holy crud! It's hot!")
}
else {
  print("Quit yer whinin'.")
}

Control Flow

Control Flow: Guard

Control Flow

Guard

func greet(person: [String: String]) {
  guard let name = person["name"] else {
    return
  }
  print("Hello \(name)!")
  
  guard let location = person["location"] else {
    print("I hope the weather is nice near you.")
    return
  }    
  print("I hope the weather is nice in \(location).")
}
 
greet(person: ["name": "John"])
greet(person: ["name": "Jane", "location": "Cupertino"])

Control Flow

Control Flow: Switch

Control Flow

Switch: Simple case

let temp = 30
switch temp {
  case 0:
    print("Brr! Freezing!")
  case 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
       11, 12, 13, 14, 15, 16, 17, 18:
    print("Still damn cold!")
  default:
    print("Meh")
}

Control Flow

Switch: Range cases

switch temp {
  case 0:
    print("Brr! Freezing!")
  case 1...32:
    print("Still damn cold!")
  default:
    print("Meh")
}

Control Flow

Switch: Local bindings

let aPoint = (2, 0)
switch aPoint {
case (let x, 0):
    print("on the x-axis with an x value of \(x)")
case (0, let y):
    print("on the y-axis with a y value of \(y)")
case let (x, y):
    print("somewhere else at (\(x), \(y))")
}

Control Flow

Switch: Where clauses

let anotherPoint = (1, -1)
switch anotherPoint {
case let (x, y) where x == y:
    print("on the identity slope")
case let (x, y) where x == -y:
    print("on the negative identity slope")
case let (x, y):
    print("somewhere else at (\(x), \(y))")
}

Control Flow

Control Flow: While

Control Flow

While

var index : Int = 0
while index < 5 {
  print("This is the \(index)th time I've said this")
  index += 1
}

Repeat-While

var anotherIndex : Int = 0
repeat {
  print("This is the \(index)th time I've said this")
  anotherIndex += 1
} while anotherIndex < 5

Control Flow

Control Flow: For

Control Flow

For-In

for index in 1...5 {
  print("\(index) times 5 equals \(index * 5)")
}
for _ in 1...5 {
  print("I don't care about the index; just print 5 times")
}

Error Handling

Errors and Exceptions and Bad Stuff, Oh My!

Error Handling

Swift Error Handling

Error Handling

Throwing an exception

Error Handling

Imagine we are building a vending machine...

struct Item {
    var price: Int
    var count: Int
}
class VendingMachine {
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]
    var coinsDeposited = 0
    func dispenseSnack(snack: String) {
        print("Dispensing \(snack)")
    }

Error Handling

Define an error type

enum VendingMachineError: Error {
    case InvalidSelection(desired: String)
    case InsufficientFunds(coinsNeeded: Int)
    case OutOfStock
}

Error Handling

Declare throws

    func vend(itemNamed name: String) throws -> String {

Error Handling

Throw the exception

        guard var item = inventory[name] else {
            throw VendingMachineError.InvalidSelection(desired: name)
        }
        
        guard item.count > 0 else {
            throw VendingMachineError.OutOfStock
        }
        
        guard item.price <= coinsDeposited else {
            throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
        }
        
        coinsDeposited -= item.price
        item.count -= 1
        inventory[name] = item
        dispenseSnack(snack: name)
        return name

Error Handling

Catching/handling exceptions

Error handling

Catch blocks

Error Handling

Catching exceptions

let vm = VendingMachine()
do {
    vm.coinsDeposited = 12
    try vm.vend(itemNamed: "Diet Coke")
}
catch VendingMachineError.OutOfStock {
    print("I don't have any of that")
}
catch VendingMachineError.InsufficientFunds(let coinsReq) {
    print("You need more money: \(coinsReq), to be precise")
}
catch VendingMachineError.InvalidSelection(let desired) 
    where desired == "Diet Coke" {
    print("Sorry, we're a Pepsi place")
}
catch VendingMachineError.InvalidSelection(_) {
    print("We don't carry that")
}
catch {
    print("We really have no idea what went wrong")
}

Error handling

"But I know it won't fail!"

Error handling

"But I know it won't fail!"

But for those occasions when that really is true...

Error Handling

Declarative try

vm.coinsDeposited = 10
let chips = try? vm.vend(itemNamed: "Chips")
print(chips)
let coke = try? vm.vend(itemNamed: "Coke")
print(coke)
let drPepper = try! vm.vend(itemNamed: "Dr Pepper")
    // BOOM: "fatal error: 'try!' expression unexpectedly ...

Functions

Make it so

Functions

Functions

Functions

Syntax

Functions

Functions

func sayHello(personName : String) -> String {
    return "Hello, \(personName)"
}
print(sayHello(personName: "Fred"))

Functions

Functions : A little more complex

func rollDice(numberOfDice : UInt, times : UInt, withBonus : UInt) -> [UInt] {
    var results : [UInt] = []
    for _ in 1...times {
        var accum : UInt = 0
        for _ in 1...numberOfDice {
            let dieRoll = UInt(arc4random_uniform(6) + 1)
            accum += dieRoll
        }
        results.append (accum + withBonus)
    }
    
    return results
}
let dieRolls = rollDice(numberOfDice: 3, times: 6, withBonus: 0)
print(dieRolls)

Functions

External parameter names

Functions

External parameter names

func rollDice(numberOfDice num : UInt, times t : UInt, withBonus bonus : UInt) -> [UInt] {
    var results : [UInt] = []
    for _ in 1...t {
        var accum : UInt = 0
        for _ in 1...num {
            let dieRoll = UInt(arc4random_uniform(6) + 1)
            accum += dieRoll
        }
        results.append (accum + bonus)
    }
  
    return results
}
let dieRolls = rollDice(numberOfDice: 3, times: 6, withBonus: 0)
print(dieRolls)

Functions

External parameter names

Functions

External parameter names

func rollDice(_ num : UInt, times t : UInt, withBonus bonus : UInt) -> [UInt] {
    var results : [UInt] = []
    for _ in 1...t {
        var accum : UInt = 0
        for _ in 1...num {
            let dieRoll = UInt(arc4random_uniform(6) + 1)
            accum += dieRoll
        }
        results.append (accum + bonus)
    }
  
    return results
}
let dieRolls = rollDice(3, times: 6, withBonus: 0)
print(dieRolls)

Functions

Default parameter values

Functions

Default parameters

func rollDice(numberOfDice : UInt, times : UInt, withBonus : UInt = 0) -> [UInt] {
    var results : [UInt] = []
    for _ in 1...times {
        var accum : UInt = 0
        for _ in 1...numberOfDice {
            let dieRoll = UInt(arc4random_uniform(6) + 1)
            accum += dieRoll
        }
        results.append (accum + withBonus)
    }
  
    return results
}
let dieRolls = rollDice(numberOfDice: 3, times: 6)
print(dieRolls)

Functions

Variadic parameters

Functions

Variadic parameters

func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}
print(arithmeticMean(1, 2, 3, 4, 5))

Functions

Pass-by-reference

Functions

Functions can also be anonymous ("closures")

Functions

Anonymous functions

let addFunc = { (left : Int, right : Int) -> Int in return left + right }
print("1 + 2 = \(addFunc(1,2))")

Inferred parameter types

let addFunc : (Int, Int) -> Int = { (left, right) in return left + right }
print("1 + 2 = \(addFunc(1,2))")

Inferred return value

let addFunc : (Int, Int) -> Int = { (left, right) in left + right }
print("1 + 2 = \(addFunc(1,2))")

Inferred parameter names

let addFunc : (Int, Int) -> Int = { $0 + $1 }
print("1 + 2 = \(addFunc(1,2))")

Functions

Functions can be treated as values

Functions

Function values

func mathOp(left : Int, right : Int, op : (Int, Int) -> Int) -> Int {
    return op(left, right)
}

Functions

Nested functions

Functions

Nested Functions

func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }
    return backwards ? stepBackward : stepForward
}
var currentValue = -2
let moveNearerToZero = chooseStepFunction(backwards: currentValue > 0)
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -2...
// -1...
// zero!

Functions

Trailing closures

Functions

Trailing closures

func mathOp(left : Int, right : Int, op : (Int, Int) -> Int) -> Int {
    return op(left, right)
}
let result = mathOp(left: 1, right: 2) { $0 + $1 }
print("Result = \(result)")

Operator Functions

Defining custom operators

Operator Functions

Operator functions

Operator Functions

Equality/Inequality operator implementation

struct Point {
  // standard properties
  var x : Int32
  var y : Int32
  init(x:Int32, y:Int32) {
    self.x = x
    self.y = y
  }
  static func ==(left: Point, right: Point) -> Bool {
    return (left.x == right.x) && (left.y == right.y)
  }
  static func !=(left: Point, right: Point) -> Bool {
    return (left.x != right.x) || (left.y != right.y)
  }
}
let origin = Point(x: 0, y: 0)
let anotherPt = Point(x: 1, y: 1)
if origin != anotherPt {
  print("Nope! Different places on the grid")
}
if origin == anotherPt {
  print("They point to the same place!")
}

Note: If you do this, you should conform to the Equatable protocol

Operator Functions

Simple operator overloading

func +(left: [Double], right: [Double]) -> [Double] {
    var sum = [Double](repeating: 0.0, count: left.count)
    for (i, _) in left.enumerated() {
        sum[i] = left[i] + right[i]
    }
    return sum
}
let result = [1.0, 2.0] + [3, 4]
print(result)

Operator Functions

Extending an operator's meaning

struct Vector: CustomStringConvertible {
  let x: Int
  let y: Int
  let z: Int
    
  var description: String {
    return "(\(x), \(y), \(z))"
  }
    
  init(_ x: Int, _ y: Int, _ z: Int) {
    self.x = x
    self.y = y
    self.z = z
  }

Operator Functions

Extending an operator's meaning

  static func +(left: Vector, right: Vector) -> Vector {
    return Vector(left.x + right.x, 
      left.y + right.y, left.z + right.z)
  }
  static prefix func -(vector: Vector) -> Vector {
    return Vector(-vector.x, -vector.y, -vector.z)
  }

Operator Functions

Operator parameters

Operator Functions

Mixed operator parameter types

func * (left: String, right: Int) -> String {
    if right <= 0 {
        return ""
    }
    var result = left
    for _ in 1..<right {
        result += left
    }
    return result
}
let scream = "a" * 6
print(scream)

Operator Functions

Custom operators

Operator Functions

Operator function types

Operator Functions

Creating the "UFO" operator for Point

infix operator <==>
extension Point {
  static func <==>(left: Point, right: Point) -> Point {
    return Point(x: left.x, y: right.y)
  }
}
let ufoPt = origin <==> anotherPt
print(ufoPt)

Simple Composite Types

Built-in composite types

Simple Composite Types

Type Aliases

Simple Composite Types

Type Aliases

typealias Age = UInt8
typealias City = (String, String, UInt32)

Simple Composite Types

Arrays []

Simple Composite Types

Arrays

var shoppingList : [String] =
  ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
print("I bought a \(shoppingList[1])")
print("She bought \(shoppingList)")
    
// Use for-in over an array
let names = ["Alice", "Bob", "Mallory", "Eve", "Trent"]
for name in names {
  print("Hello, \(name)!")
}

Simple Composite Types

Dictionaries []

Simple Composite Types

Dictionaries

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
let malJob = occupations["Malcolm"]
print("Mal is the \(malJob)")
print("People work as \(occupations)")
    
var explore = [ 4.5 : "StringValue" ]
print("Can we key by doubles? \(explore[4.5])")
    
// Use for-in over dictionaries
for (key, value) in occupations {
  print("\(key) is the \(value)")
}

Simple Composite Types

Optional Types

Simple Composite Types

Optionals

var theAnswer = 42
var maybeTheAnswer : Int? = theAnswer
maybeTheAnswer = nil
print("The answer is \(theAnswer)")
//print("... but it might be \(maybeTheAnswer!)")
    // Since maybeTheAnswer is nil, this will generate
    // an exception at runtime
if (maybeTheAnswer != nil) {
    print("The answer is \(maybeTheAnswer!)")
}

Simple Composite Types

Enumerations

Simple Composite Types

Enums

enum CompassPoint {
    case North
    case South
    case East
    case West
}
enum Planet {
    case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
    
var direction = CompassPoint.North
switch direction {
    case .North: print("Brrr cold")
    case .South: print("Watch out for penguins")
    case .West : print("Where rises the Sun")
    case .East : print("Where sets the Sun")
}

Simple Composite Types

Enum raw values

enum Planet: Int {
    case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
let planet = Planet(rawValue: 3)
  // planet is set to .Earth
let planetIndex = Planet.Earth.rawValue
  // planetIndex is 3

Simple Composite Types

Enums associated values

enum Barcode {
  case UPCA(Int, Int, Int)
  case QRCode(String)
}
let barcode = Barcode.UPCA(8, 85909_51226, 3)
// or
let barcode2 = Barcode.QRCode("8A9873CEFAE")

Simple Composite Types

Tuples

Simple Types

Tuples

var city = ("Seattle", "Washington", 5000000)
var person = ("Teresa", "Nguyen", 39)
var ssi = city
ssi = person
    // Structurally typed!
print(ssi)
print(ssi.0)

Simple Composite Types

Tuples

var team : (name : String, association : String, wins : Int) =
    ("Sounders", "MLS", 15)
print(team)
print("The \(team.name) have won \(team.wins) times")

Simple Composite Types

Tuples and Switch

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("(0, 0) is at the origin")
case (_, 0):
    print("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
    print("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
    print("(\(somePoint.0), \(somePoint.1)) is inside the box")
default:
    print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
}

Complex Composite Types

Making molecules out of atoms

Complex Composite Types

Structures AND Classes

Complex Composite Types

Structures

Complex Composite Types

Classes

Complex Composite Types

Instantiation (both)

Complex Composite Types

Creating a new instance

var aPoint = Point(x:2, y:3)
print("aPoint = \(aPoint.stringified)")
var anotherPoint = aPoint
aPoint.x = 12
print("anotherPoint = \(anotherPoint), aPoint = \(aPoint)")

Complex Composite Types

Equivalence/Identity (classes)

Complex Composite Types

Reference identity

var ted = Person(firstName:"Ted", lastName:"Neward", age:42, location:nil)
var ted2 = ted
  // These point to the same object
ted.firstName = "Theodore"
print(ted.stringified)
print(ted2.stringified) // "Theodore"

Complex Composite Types

Equivalence/Identity (structs)

Properties

Representing 'fields' in Swift

Properties

Properties

Properties

Different kinds of properties

Properties

Variable stored properties

Properties

Variable stored properties

var name: String

Properties

Constant stored properties

Properties

Constant stored properties

let starterYear = 2017

Properties

Static properties

Properties

Lazy stored properties

Properties

Lazy stored properties

lazy var complexObject = ComplexObject()

Properties

Computed (unstored) properties

Properties

Computed (unstored) properties

var origin = Point()
var size = Size()
var center: Point {
    get {
        let centerX = origin.x + (size.width / 2)
        let centerY = origin.y + (size.height / 2)
        return Point(x: centerX, y: centerY)
    }
    set(newCenter) {
        origin.x = newCenter.x - (size.width / 2)
        origin.y = newCenter.y - (size.height / 2)
    }
}

Properties

Observed properties

Properties

Observed properties

var totalSteps: Int = 0 {
    willSet(newTotalSteps) {
        print("About to set totalSteps to \(newTotalSteps)")
    }
    didSet {
        if totalSteps > oldValue  {
            print("Added \(totalSteps - oldValue) steps")
        }
    }
}

Methods

Adding behavior to Swift

Methods

Methods

Methods

Defining and calling methods on structs

// ... still in struct Point ...
  func stringRep() -> String {
    return "(\(self.x),\(self.y))"
  }
  static func compare(left: Point, right: Point) -> Bool {
    return (left.x == right.x) && (left.y == right.y)
  }
var o = Point.ORIGIN
print(o.stringRep())
if Point.compare(left: o, right: Point.ORIGIN) {
  print("Yep, they point to the same place")
}
o.move(x: 3, y:5)
print(o.stringRep())

Methods

Initializers

Methods

Initializers

  init(x:Int32, y:Int32) {
    self.x = x
    self.y = y
  }
  init(x:Int32, y:Int32, name: String) {
    self.x = x
    self.y = y
    self.name = name
  }
  init(y: Int32, x: Int32) {
    self.x = x
    self.y = y
  }
  init(pt: Point, offsetX: Int32 = 0, offsetY : Int32 = 0) {
    self = pt
    self.move(x: offsetX, y:offsetY)
  }
}

Complex Composite Types

Failable initializers

Complex Composite Types

Deinitialization

Complex Composite Types

Imagine a game of Poker, with a fixed amount of coins...

class Player {
  var coinsInPurse: Int
  init(coins: Int) {
    coinsInPurse = Bank.vendCoins(coins)
  }
  func winCoins(coins: Int) {
    coinsInPurse += Bank.vendCoins(coins)
  }
  deinit {
    Bank.receiveCoins(coinsInPurse)
  }
}

Methods

"Mutating" methods

Reference Counting

What's in your wallet (reference chain)?

Reference Counting

Automatic Reference Counting

Reference Counting

Typical usage

Reference Counting

Typical code

{ // new scope block
    x = Object() // call this object "A"; A's refct = 1
    y = Object() // call this object "B"; B's refct = 1
}
// when y goes out of scope, it drops its reference to B
// B's refct == 0; B is now deallocated
// when x goes out of scope, it drops its reference to A
// A's refct == 0; A is now deallocated

Reference Counting

Typical code

x = Object() // call this object "A"; A's refct = 1
{ // new scope block
    y = Object() // call this object "B"; B's refct = 1

    // when y goes out of scope, it drops its reference to B
    // B's refct == 0; B is now deallocated
}
// x is not out of scope (A.refct == 1) here, so A lives on

Reference Counting

Typical code

x = Object() // call this object "A"; A's refct = 1
{ // new scope block
    y = x; // this is A, so A's refct = 2

    // when y goes out of scope, it drops its reference to A
    // A's refct = 1; A remains allocated
}
// x is not out of scope (A.refct == 1) here, so A lives on

Reference Counting

Reference cycles

Reference Counting

Reference cycle memory leak

class Person {
  let firstName : String
  let lastName : String
  var spouse : Person?

  init(first: String, last: String) {
      firstName = first
      lastName = last
  }
  deinit {
    print("\(firstName) is being deinitialized")
  }
}

{
    var b = Person(first: "Brad", last: "Pitt")
    var a = Person(first: "Angela", last: "Jolie")
    b.spouse = angelina
    a.spouse = brad
}
// They never go away....

Reference Counting

Reference cycles

Reference Counting

Strong References

Reference Counting

Weak References

Reference Counting

Weak references

class Person {
  let firstName : String
  let lastName : String
  weak var spouse : Person?

  init(first: String, last: String) {
      firstName = first
      lastName = last
  }
  deinit {
    print("\(firstName) is being deinitialized")
  }
}

{
    var b = Person(first: "Brad", last: "Pitt")
    var a = Person(first: "Angela", last: "Jolie")
    b.spouse = angelina
    a.spouse = brad
}

Reference Counting

Reference cycles

Reference Counting

Unowned References

Reference Counting

An unowned reference example

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}

class CreditCard {
    let number: UInt64
    unowned var customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    func setCustomer(customer: Customer) {
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}
var john = Customer(name: "John Appleseed")
john.card = CreditCard(number: 1234_5678_9012_3456, customer: john)

Inheritance

IS-A relationships in Swift

Inheritance

Inheritance

Inheritance

Overriding initializers

Overriding properties

Overriding methods

Inheritance

Typecasting: moving up/down the inheritance hierarchy

Access Control

Keeping private things, private

Access Control

Fundamental unit in Swift is the "module"

Modules are made up of "source files"

Access Control

Access Control basics

Access Control

General principle of usage:

Access Control

Class' access level

Enums

Access Control

Tuples

Access Control

Functions

Access Control

Constants, Variables, Properties

Access Control

Properties can have differing access levels

Protocols

You must conform!

Protocols

Protocols

Protocols

protocol FullyNamed {
    var fullName : String { get }
}
    
protocol RandomNumberGenerator {
    func random() -> Double
}

Protocols

Implementing a protocol

Protocols

struct Person : FullyNamed {
    var fullName : String
}
    
class LinearCongruentialGenerator: 
    RandomNumberGenerator {
    
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
      lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
      return lastRandom / m
    }
}

Protocols

Delegation

Protocols

class Dice {
    let sides : Int
    let generator : RandomNumberGenerator
    init(sides: Int, generator: RandomNumberGenerator) {
      self.sides = sides
      self.generator = generator
    }
    func roll() -> Int {
      return Int(generator.random() * Double(sides)) + 1
    }
}
protocol DiceGame {
    var dice : Dice { get }
    func play()
}
protocol DiceGameDelegate {
    func gameDidStart(game: DiceGame)
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(game: DiceGame)
}

Selectors

Dynamic programming in Swift

Selectors

Selectors

Selectors

Restrictions

Selectors

Syntax

Selectors

Example

class SomeClass: NSObject {
  @objc let property: String

  @objc(doSomethingWithInt:)
  func doSomething(_ x: Int) { }

  init(property: String) { self.property = property }
}
let selectorForMethod = #selector(SomeClass.doSomething(_:))
let selectorForPropertyGetter = #selector(getter: SomeClass.property)

Key-Value Coding

Flexibility in Swift

Key-Value Coding

Swift is designed to be compatible with Objective-C

Key-Value Coding

Definitions

Key-Value Coding

Key-Value coding features

Key-Value Coding

Identifying an object's properties with keys and keypaths

Key-Value Coding

Consider this simple class

class Person : NSObject {
  @objc dynamic var firstName = ""
  @objc dynamic var lastName = ""
  @objc dynamic var age = 0
}
let p = Person()
p.firstName = "Fred"

We can access the property by "key"

let pkeyPath = #keyPath(Person.firstName)
if let value = p.value(forKey: pkeyPath) {
  print(value)  // "Fred"
}
if let value = p.value(forKey: "firstName") {
  print(value)  // "Fred"
}

Key-Value Coding

Collection properties

Key-Value Coding

Collection operators

Key-Value Coding

Transaction class

class Transaction: NSObject {
  @objc var payee: String
  @objc var amount: Float
  @objc var date: Date
  init(_ p: String, _ a: Float, _ d: Date) {
    self.payee = p
    self.amount = a
    self.date = d
  }
}

Key-Value Coding

Using collection operators

let transactions = [
  Transaction("Green Power", 120.00, "2015-12-01".toDateTime()),
  Transaction("Cable", 130.00, "2015-12-01".toDateTime()),
  Transaction("Green Power", 150.00, "2016-01-01".toDateTime()),
  Transaction("Mortgage", 1250.00, "2016-01-05".toDateTime()),
  Transaction("Car Loan", 250.00, "2016-01-15".toDateTime()),
  Transaction("Green Power", 170.00, "2016-02-01".toDateTime()),
  Transaction("Car Loan", 250.00, "2016-02-15".toDateTime()),
  Transaction("Car Loan", 250.00, "2016-03-15".toDateTime()),
]
print((transactions as NSArray).value(forKeyPath:"@avg.amount")!)
print((transactions as NSArray).value(forKeyPath:"@max.date")!)
print((transactions as NSArray).value(forKeyPath:"@count")!)

Key-Value Coding

Observers

Key-Value Coding

Adding in observers

// recall that p = Person(), p.firstName = "Fred"
// recall that pkeyPath = #keyPath(Person.firstName)
let result = p.observe(\.firstName) { person, change in
  print("firstName change on \(person) to \(change)")
} 
let result2 = p.observe(\.firstName, options: [.old, .new]) { person, change in
  print("\(person) changed to \(change)")
}
p.setValue("Barney", forKey: pkeyPath)
p.setValue("Wilma", forKey: "firstName")
p.firstName = "Fred"

Key-Value Coding

Adding in "formal" observers

class Observer : NSObject {
  override func observeValue(forKeyPath keyPath: String?, 
      of object: Any?, change: [NSKeyValueChangeKey : Any]?, 
      context: UnsafeMutableRawPointer?) {
    if keyPath == "firstName" {
      print("Observing a change to firstName: \(change!) on \(object!)")
    }
  }
}
let o = Observer()
p.addObserver(o, forKeyPath: "firstName", options: .new, context: nil)
p.setValue("Barney", forKey: "firstName")
print(p.firstName)

Key-Value Coding

References

Protocols

You must conform!

Protocols

Protocols

Protocols

protocol FullyNamed {
    var fullName : String { get }
}
    
protocol RandomNumberGenerator {
    func random() -> Double
}

Protocols

Implementing a protocol

Protocols

struct Person : FullyNamed {
    var fullName : String
}
    
class LinearCongruentialGenerator: 
    RandomNumberGenerator {
    
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
      lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
      return lastRandom / m
    }
}

Protocols

Delegation

Protocols

class Dice {
    let sides : Int
    let generator : RandomNumberGenerator
    init(sides: Int, generator: RandomNumberGenerator) {
      self.sides = sides
      self.generator = generator
    }
    func roll() -> Int {
      return Int(generator.random() * Double(sides)) + 1
    }
}
protocol DiceGame {
    var dice : Dice { get }
    func play()
}
protocol DiceGameDelegate {
    func gameDidStart(game: DiceGame)
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(game: DiceGame)
}

Extensions

Guess what you can do now?

Extensions

Extensions

Extensions

Defining

Extensions

Adding units-of-measure conversions

extension Double {
  var km: Double { return self * 1_000.0 }
  var m: Double { return self }
  var cm: Double { return self / 100.0 }
  var mm: Double { return self / 1_000.0 }
  var ft: Double { return self / 3.28084 }
}
print("3ft equals \(3.ft) meters")

Extensions

Add a Ruby-like "repetitions" ability to Int

extension Int {
  func repetitions(task: () -> Void) {
    for _ in 0..<self {
      task()
    }
  }
}
3.repetitions {
  print("Hello!")
}

Extensions

Let's add some useful functionality to String

extension String {
  func toDateTime() -> Date {
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd"
    return dateFormatter.date(from: self)!
  }
}

Generics

Generics

Generics: parameterized types

Metatypes

Types of types

Metatypes

Metatypes are types that describe types

Metatypes

Metatype usage

Macros

Metaprogramming Swift with semantic macros

Macros

Source metaprogramming

Macros

Two kinds of macros

Macros

Swift macro types

Macros

Freestanding macros: "#function" and "#warning"

func myFunction() {
    print("Currently running \(#function)")
    #warning("Something's wrong")
}

#function returns the name of the currently-scoped function

#warning issues a warning during compilation

Macros

An attached macro: "OptionSet"

@OptionSet<Int>
struct SundaeToppings {
    private enum Options: Int {
        case nuts
        case cherry
        case fudge
    }
}

This macro sets "nuts", "cherry", and "fudge" to be "bit flags" values (1, 2, 4, ...)

Macros

Predefined Macros

Macros

Implementation

Macros

Implementing a macro

Macros

Implementing a macro

Macros

Package declaration

Macros

Package declaration

// swift-tools-version: 5.9

import PackageDescription
import CompilerPluginSupport

let package = Package(
    name: "MyPackage",
    platforms: [ .iOS(.v17), .macOS(.v13)],
    // ...
)

Macros

Package targets

Macros

Package targets

targets: [
    // Macro implementation that performs the source transformations
    .macro(
        name: "MyProjectMacros",
        dependencies: [
            .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
            .product(name: "SwiftCompilerPlugin", package: "swift-syntax")
        ]
    ),

    // Library that exposes a macro as part of its API
    .target(name: "MyProject", dependencies: ["MyProjectMacros"]),
]

Macros

Create the macro type

Macros

The macro's AST type

import SwiftSyntax
import SwiftSyntaxMacros

enum CustomError: Error { case message(String) }
public struct FourCharacterCode: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) throws -> ExprSyntax {
        guard let argument = node.argumentList.first?.expression,
              let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
              segments.count == 1,
              case .stringSegment(let literalSegment)? = segments.first
        else {
            throw CustomError.message("Need a static string")
        }

        let string = literalSegment.content.text
        guard let result = fourCharacterCode(for: string) else {
            throw CustomError.message("Invalid four-character code")
        }

        return "\(raw: result) as UInt32"
    }
}

Macros

The actual string-to-Int code

private func fourCharacterCode(for characters: String) -> UInt32? {
    guard characters.count == 4 else { return nil }

    var result: UInt32 = 0
    for character in characters {
        result = result << 8
        guard let asciiValue = character.asciiValue else { return nil }
        result += UInt32(asciiValue)
    }
    return result
}

Summary

Swift is...

Credentials

Who is this guy?