Swift 5.3 - Section 4: Advanced Topics

Swift 5.3 - Section 4: Advanced Topics

For this series of posts I have used extensively the book Swift Apprentice by Ray Wenderlich.
I strongly suggest this book, because if you buy the digital copy, Ray Wenderlich guarantees updates for the rest of the life!

I have also to say that Ray Wenderlich offers a cheat sheet available for download for free.

The purpose of these post is not to copy the book itself, that I strongly suggest to read, also doing the suggested exercises, that give the required confidence to really learn the language.
In particular I will not explain how to use Xcode or the playgrounds. The idea is that you buy the book and read it!

Final note: my background is deep experience in C#, so maybe some concepts are obvious for me. If you read this article, find it useful, but would like that I add something, don't hesitate to comment here below! But remember, my goal is not to copy the book :-)

Chapter 18: Access Control and Code Organization

Introducing access control

private(set) var balance: Dollars

Modifiers available in Swift:

  • private: accessible only to the defining type, plus its nested types, and extension on the type within the same source file;
  • fileprivate: accessible only to the source file where it is defined (for example, to allow the creation of instances of one class only from another class);
  • internal: accessible only within the module where it is defined (this is the default access level);
  • public: accessible anywhere;
  • open: public, plus it's possible to override it in anther module.

Organizing code into extensions

Extension by behavior: adding extension to a class to add functionalities.
It's possible to declare an extension as private. This means that automatically all members of the extension are private by default.

Extensions by protocol conformance

Another technique is to organize extensions by protocol conformance:

extension CheckingAccount: CustomStringConvertible {
  public var description: String {
    "Checking Balance: $\(balance)"
  }
}

available()

available() is a way to deprecate code before removing it completely:

@available(*, deprecated, message: "Use init(interestRate:pin:) instead")

@available(*, deprecated, message: "Use processInterest(pin:) instead")

The star denotes what platforms are affected by the deprecation: *, iOS, iOSMac, tvOS, watchOS.

Opaque return types

Let's suppose you have a function and want to return an instance of a type: you need to use the some keyword:

func createAccount() -> some Account {
  CheckingAccount()
}

Testing

import XCTest

class BankingTests: XCTestCase {
  func testSomething() {
  }
}

To run the tests from the Playground:

BankingTests.defaultTestSuite.run()

XCTAssert

XCTAssert functions are used in tests to assert that certain conditions are met:

func testNewAccountBalanceZero() {
  let checkingAccount = CheckingAccount()
  XCTAssertEqual(checkingAccount.balance, 0)
}

func testCheckOverBudgetFails() {
  let checkingAccount = CheckingAccount()
  let check = checkingAccount.writeCheck(amount: 100)
  XCTAssertNil(check)
}

XCFail and XCTSkip

XCFail is used to fail the test if some preconditions are not met, for example running the test on an old version of the simulator:

func testNewAPI() {
  guard #available(iOS 14, *) else {
    XCFail("Only available on iOS 14 and above")
    return
  }
  // perform test
}

In alternative to fail it, you can use XCTSkip to skip the test:

func testNewAPI() {
  guard #available(iOS 14, *) else {
    throw XCTSkip("Only available on iOS 14 and above")
    return
  }
  // perform test
}

Making things @testable

To make your internal interface visible to tests (private members remain private):

@testable import Banking

The setUp and tearDown methods

The setUp() method is executed before each test and the tearDown() method is executed after each test:

var checkingAccount: CheckingAccount!

override func setUp() {
  super.setUp()
  checkingAccount = CheckingAccount()
}

override func tearDown() {
  checkingAccount.withdraw(amount: checkingAccount.balance)
  super.tearDown()
}

Chapter 19: Custom Operators, Subscripts & Keypaths

Types of operators

  • Unary: ! (prefix), as (postfix);
  • *Binary": +, -, *, /, %, ==, !=, <, >, <=, >=, &&, ||
  • Ternary: ? :

Your own operator

It's possible to define custom operators and use any character combinations:

infix operator **

func **(base: Int, power: Int) -> Int {
  precondition(power >= 2)
  var result = base
  for _ in 2...power {
    result *= base
  }
  return result
}

let base = 2
let exponent = 2
let result = base ** exponent

Compound assignment operator

Most built-in operators have a corresponding compound assignment operator:

infix operator **=

func **=(lhs: inout Int, rhs: Int) {
  lhs = lhs ** rhs
}

var number = 2
number **= exponent

Generic operators

To allow the operator to work with all kind of integer types:

func **<T: BinaryInteger>(base: T, power: Int) -> T {
  precondition(power >= 2)
  var result = base
  for _ in 2...power {
    result *= base
  }
  return result
}

func **=<T: BinaryInteger>(lhs: inout T, rhs: Int) {
  lhs = lhs ** rhs
}

let unsignedBase: UInt = 2
let unsignedResult = unsignedBase ** exponent

let base8: Int8 = 2
let result8 = base8 ** exponent

let unsignedBase8: UInt8 = 2
let unsignedResult8 = unsignedBase8 ** exponent

let base16: Int16 = 2
let result16 = base16 ** exponent

let unsignedBase16: UInt16 = 2
let unsignedResult16 = unsignedBase16 ** exponent

let base32: Int32 = 2
let result32 = base32 ** exponent

let unsignedBase32: UInt32 = 2
let unsignedResult32 = unsignedBase32 ** exponent

let base64: Int64 = 2
let result64 = base64 ** exponent

let unsignedBase64: UInt64 = 2
let unsignedResult64 = unsignedBase64 ** exponent

Precedence and associativity

If you want to use the new operator together with other operators, you need to provide information about precedence and associativity:

precedencegroup ExponentiationPrecedence {
  associativity: right
  higherThan: MultiplicationPrecedence
}

infix operator **: ExponentiationPrecedence

So now you can use it, without the need of parenthesis, in:

2 * 2 ** 3 ** 2

You could also have chosen associativity: none and forced to use parenthesis.

Subscripts

How to overload the [] operator:

subscript(parameterList) -> ReturnType {
  get {
    // return someValue of ReturnType
  }
  set(newValue) {
    // set someValue of ReturnType to newValue
  }
}

For example:

class Person {
  let name: String
  let age: Int
  
  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

extension Person {
  subscript(key: String) -> String? {
    switch key {
      case "name": return name
      case "age": return "\(age)"
      default: return nil
    }
  }
}

let me = Person(name: "Cosmin", age: 33)
me["name"]
me["age"]
me["gender"]

Subscripts parameters

You can add external parameter names to subscripts:

subscript(property key: String) -> String? {
  // original code
}

me[property: "name"]
me[property: "age"]
me[property: "gender"]

Static subscripts

class File {
  let name: String
  
  init(name: String) {
    self.name = name
  }
  
  static subscript(key: String) -> String {
    switch key {
      case "path": return "custom path"
      default: return "default path"
    }
  }
}


File["path"]
File["PATH"]

Dynamic member lookup

@dynamicMemberLookup
class Instrument {
  let brand: String
  let year: Int
  private let details: [String: String]
  
  init(brand: String, year: Int, details: [String: String]) {
    self.brand = brand
    self.year = year
    self.details = details
  }
  
  subscript(dynamicMember key: String) -> String {
    switch key {
      case "info": return "\(brand) made in \(year)."
      default: return details[key] ?? ""
    }
  }
}

let instrument = Instrument(brand: "Roland", year: 2019, details: ["type": "acoustic", "pitch": "C"])
instrument.info
instrument.pitch

A derived class will inherit dynamic member lookup automatically:

class Guitar: Instrument {}
let guitar = Guitar(brand: "Fender", year: 2019, details: ["type": "electric", "pitch": "C"])
guitar.info

It's also possible to use dynamic member lookup for static subscripts:

@dynamicMemberLookup
class Folder {
  let name: String
  
  init(name: String) {
    self.name = name
  }
  
  class subscript(dynamicMember key: String) -> String {
    switch key {
      case "path": return "custom path"
      default: return "default path"
    }
  }
}

Folder.path
Folder.PATH

Keypaths

Keypaths are reference to properties, done via "":

class Tutorial {
  let title: String
  let author: Person
  let details: (type: String, category: String)
  
  init(title: String, author: Person, details: (type: String, category: String)) {
    self.title = title
    self.author = author
    self.details = details
  }
}

let tutorial = Tutorial(title: "Object Oriented Programming in Swift", author: me, details: (type: "Swift", category: "iOS"))

let title = \Tutorial.title
let tutorialTitle = tutorial[keyPath: title]

Keypaths can also go more levels down:

let authorName = \Tutorial.author.name
var tutorialAuthor = tutorial[keyPath: authorName]

How to use keypaths with tuples:

let type = \Tutorial.details.type
let tutorialType = tutorial[keyPath: type]
let category = \Tutorial.details.category
let tutorialCategory = tutorial[keyPath: category]

Appending keypaths

let authorPath = \Tutorial.author
let authorNamePath = authorPath.appending(path: \.name)
tutorialAuthor = tutorial[keyPath: authorNamePath]

Setting properties with keypaths

class Jukebox {
  var song: String
  
  init(song: String) {
    self.song = song
  }
}

let jukebox = Jukebox(song: "Nothing Else Matters")

let song = \Jukebox.song
jukebox[keyPath: song] = "Stairway to Heaven"

Keypath member lookup

It's possible to combine dynamic member lookups with keypaths:

struct Point {
  let x, y: Int
}

@dynamicMemberLookup
struct Circle {
  let center: Point
  let radius: Int
  
  subscript(dynamicMember keyPath: KeyPath<Point, Int>) -> Int {
    center[keyPath: keyPath]
  }
}

let center = Point(x: 1, y: 2)
let circle = Circle(center: center, radius: 1)
circle.x
circle.y

Chapter 20: Pattern Matching

If and guard

If with pattern matching:

func process(point: (x: Int, y: Int, z: Int)) -> String {
  if case (0, 0, 0) = point {
    return "At origin"
  }
  return "Not at origin"
}

let point = (x: 0, y: 0, z: 0)
let status = process(point: point) // At origin

Guard with pattern matching:

func process(point: (x: Int, y: Int, z: Int)) -> String {
  guard case (0, 0, 0) = point else {
    return "Not at origin"
  }
  // guaranteed point is at the origin
  return "At origin"
}

Switch

func process(point: (x: Int, y: Int, z: Int)) -> String {
  let closeRange = -2...2
  let midRange = -5...5
  
  switch point {
    case (0, 0, 0):
      return "At origin"
    case (closeRange, closeRange, closeRange):
      return "Very close to origin"
    case (midRange, midRange, midRange):
      return "Nearby origin"
    default:
      return "Not near origin"
  }
}

let point = (x: 15, y: 5, z: 3)
let status = process(point: point) // Not near origin

for

Pattern matching can be used as a filter in a for loop:

let groupSizes = [1, 5, 4, 6, 2, 1, 3]
for case 1 in groupSizes {
  print("Found an individual") // 2 times
}

Wildcard pattern

if case (_, 0, 0) = coordinate {
  // x can be any value. y and z must be exactly 0.
  print("On the x-axis") // Printed!
}

Value-binding pattern

You use var or let to declare a variable or constant while doing pattern matching:

if case (let x, 0, 0) = coordinate {
  print("On the x-axis at \(x)") // Printed: 1
}

To bind multiple values:

if case let (x, y, 0) = coordinate {
  print("On the x-y plane at (\(x), \(y))") // Printed: 1, 0
}

Enumerate case pattern

enum Direction {
  case north, south, east, west
}

let heading = Direction.north

if case .north = heading {
  print("Don’t forget your jacket") // Printed!
}

Enumeration combined with value binding pattern:

enum Organism {
  case plant
  case animal(legs: Int)
}

let pet = Organism.animal(legs: 4)

switch pet {
  case .animal(let legs):
    print("Potentially cuddly with \(legs) legs") // Printed: 4
  default: print("No chance for cuddles")
}

Optional pattern

Check for non-null values:

let names: [String?] = ["Michelle", nil, "Brandon", "Christine", nil, "David"]

for case let name? in names {
  print(name) // 4 times
}

"Is" type-casting pattern

Is is used to check if a value is of a particular type:

let response: [Any] = [15, "George", 2.0]

for element in response {
  switch element {
    case is String:
      print("Found a string") // 1 time
    default:
      print("Found something else") // 2 times
  }
}

"As" type-casting pattern

for element in response {
  switch element {
    case let text as String:
      print("Found a string: \(text)") // 1 time
    default:
      print("Found something else") // 2 times
  }
}

Qualifying with where

You can add a where clause to pattern matching:

for number in 1...9 {
  switch number {
    case let x where x % 2 == 0:
      print("even") // 4 times
    default:
      print("odd") // 5 times
  }
}

Chaining with commas

It's possible to chain multiple patterns in a single case:

func timeOfDayDescription(hour: Int) -> String {
  switch hour {
    case 0, 1, 2, 3, 4, 5:
      return "Early morning"
    case 6, 7, 8, 9, 10, 11:
      return "Morning"
    case 12, 13, 14, 15, 16:
      return "Afternoon"
    case 17, 18, 19:
      return "Evening"
    case 20, 21, 22, 23:
      return "Late evening"
    default:
      return "INVALID HOUR!"
  }
}
let timeOfDay = timeOfDayDescription(hour: 12) // Afternoon

Sample filtering with bind value:

if case .animal(let legs) = pet, case 2...4 = legs {
  print("potentially cuddly") // Printed!
} else {
  print("no chance for cuddles")
}

Another sample: what follows

enum Number {
  case integerValue(Int)
  case doubleValue(Double)
  case booleanValue(Bool)
}

let a = 5
let b = 6
let c: Number? = .integerValue(7)
let d: Number? = .integerValue(8)

if a != b {
  if let c = c {
    if let d = d {
      if case .integerValue(let cValue) = c {
        if case .integerValue(let dValue) = d {
          if dValue > cValue {
            print("a and b are different") // Printed!
            print("d is greater than c") // Printed!
            print("sum: \(a + b + cValue + dValue)") // 26
          }
        }
      }
    }
  }
}

can be shortened to:

if a != b,
  let c = c,
  let d = d,
  case .integerValue(let cValue) = c,
  case .integerValue(let dValue) = d,
  dValue > cValue {
    print("a and b are different") // Printed!
    print("d is greater than c") // Printed!
    print("sum: \(a + b + cValue + dValue)") // Printed: 26
}

Custom tuple

It's possible to create a tuple on the fly to match it:

let name = "Bob"
let age = 23

if case ("Bob", 23) = (name, age) {
  print("Found the right Bob!") // Printed!
}

Another sample:

var username: String?
var password: String?

switch (username, password) {
  case let (username?, password?):
    print("Success! User: \(username) Pass: \(password)")
  case let (username?, nil):
    print("Password is missing. User: \(username)")
  case let (nil, password?):
    print("Username is missing. Pass: \(password)")
  case (nil, nil):
    print("Both username and password are missing") // Printed!
}

Fun with wildcards

This is the basic sample:

for _ in 1...3 {
  print("hi") // 3 times
}

Verify that an optional exists:

let user: String? = "Bob"
guard let _ = user else {
  print("There is no user.")
  fatalError()
}
print("User exists, but identity not needed.") // Printed!

But the best way to validate against an optional is still:

guard user != nil else {
  print("There is no user.")
  fatalError()
}

Final sample:

struct Rectangle {
  let width: Int
  let height: Int
  let background: String
}

let view = Rectangle(width: 15, height: 60, background: "Green")
switch view {
  case _ where view.height < 50:
    print("Shorter than 50 units")
  case _ where view.width > 20:
    print("Over 50 tall, & over 20 wide")
  case _ where view.background == "Green":
    print("Over 50 tall, at most 20 wide, & green") // Printed!
  default:
    print("This view can’t be described by this example")
}

Overloading ~=

It's possible to overload the ~= operator to provide your own pattern matching:

func ~=(pattern: [Int], value: Int) -> Bool {
  for i in pattern {
    if i == value {
      return true
    }
  }
  return false
}

And now you can use it in this way:

let list = [0, 1, 2, 3]
let integer = 2

let isInArray = (list ~= integer) // true

if case list = integer {
  print("The integer is in the array") // Printed!
} else {
  print("The integer is not in the array")
}

Chapter 21: Error Handling

Failable initializers

If you try to initialize an integer or an enum, it could fail, and return nil.

let value = Int("3") // Optional(3)
let failedValue = Int("nope") // nil

enum PetFood: String {
  case kibble, canned
}
let morning = PetFood(rawValue: "kibble") // Optional(.kibble)
let snack = PetFood(rawValue: "fuuud!") // nil

You can write your own failable initializers:

struct PetHouse {
  let squareFeet: Int
  
  init?(squareFeet: Int) {
    if squareFeet < 1 {
      return nil
    }
    self.squareFeet = squareFeet
  }
}

let tooSmall = PetHouse(squareFeet: 0) // nil
let house = PetHouse(squareFeet: 1) // Optional(Pethouse)

Optional chaining

In case you have a deep structure with nested optional, you can check them with optional chaining:

if let sound = janie.pet?.favoriteToy?.sound {
  print("Sound \(sound).")
} else {
  print("No sound.")
}

Map and compactMap

In case you have a vector, you can project it to properties that can be nil:

let team = [janie, tammy, felipe]

let petNames = team.map { $0.pet?.name }
// > Optional("Delia")
// > Optional("Evil Cat Overlord")
// > nil

To skip the nil, you can use compactMap:

let betterPetNames = team.compactMap { $0.pet?.name }
// > Delia
// > Evil Cat Overlord

Note that skipping the nils, the results are not optionals anymore.

Error protocol

The Error protocol is like an exception. It suits well to make it an Enum:

class Pastry {
  let flavor: String
  var numberOnHand: Int
  
  init(flavor: String, numberOnHand: Int) {
    self.flavor = flavor
    self.numberOnHand = numberOnHand
  }
}

enum BakeryError: Error {
  case tooFew(numberOnHand: Int)
  case doNotSell
  case wrongFlavor
}

Throwing errors

You raise errors throwing them:

class Bakery {
  var itemsForSale = [
    "Cookie": Pastry(flavor: "ChocolateChip", numberOnHand: 20),
    "PopTart": Pastry(flavor: "WildBerry", numberOnHand: 13),
    "Donut" : Pastry(flavor: "Sprinkles", numberOnHand: 24),
    "HandPie": Pastry(flavor: "Cherry", numberOnHand: 6)
  ]
  
  func open(_ now: Bool = Bool.random()) throws -> Bool {
    guard now else {
      throw Bool.random() ? BakeryError.inventory
                          : BakeryError.noPower
    }
  }

  func orderPastry(item: String, amountRequested: Int, flavor: String) throws -> Int {
    guard let pastry = itemsForSale[item] else {
      throw BakeryError.doNotSell
    }
    guard flavor == pastry.flavor else {
      throw BakeryError.wrongFlavor
    }
    guard amountRequested <= pastry.numberOnHand else {
      throw BakeryError.tooFew(numberOnHand: pastry.numberOnHand)
    }
    pastry.numberOnHand -= amountRequested
    
    return pastry.numberOnHand
  }
}

Handling errors

You handle errors catching them:

do {
  try bakery.open()
  try bakery.orderPastry(item: "Albatross", amountRequested: 1, flavor: "AlbatrossFlavor")
} catch BakeryError.invetory, BakeryError.noPower {
  print("Sorry, the bakery is now closed.")
} doNotSell {
  print("Sorry, but we don’t sell this item.")
} catch BakeryError.wrongFlavor {
  print("Sorry, but we don’t carry this flavor.")
} catch BakeryError.tooFew {
  print("Sorry, we don’t have enough items to fulfill your order.")
}

Code that can throw errors must always be inside a do block.

Not looking at the detailed error

If you don't care about the error and you only want to check that there are no errors:

let open = try?.bakery.open(false)
let remaining = try? bakery.orderPastry(item: "Albatross", amountRequested: 1, flavor: "AlbatrossFlavor")

Stopping your program on an error

If want to check for any error, you can catch without anything specific:

do {
  try bakery.open(true)
  try bakery.orderPastry(item: "Cookie", amountRequested: 1, flavor: "ChocolateChip")
} catch {
  fatalError()
}

Or a shortcut:

try! bakery.open(true)
try! bakery.orderPastry(item: "Cookie", amountRequested: 1, flavor: "ChocolateChip")

Handling multiple errors

func moveSafely(_ movement: () throws -> ()) -> String {
  do {
    try movement()
    return "Completed operation successfully."
  } catch PugBotError.invalidMove(let found, let expected) {
    return "The PugBot was supposed to move \(expected), but moved \(found) instead."
  } catch PugbotError.endOfPath {
    return "The PugBot tried to move past the end of the path."
  } catch {
    return "An unknown error occurred."
  }
}

Rethrows

A function that takes a throwing closure parameter can choose to rethrow:

func perform(times: Int, movement: () throws -> ()) rethrows {
  for _ in 1...times {
    try movement()
  }
}

GDC (Grand Central Dispatch)

It's possible to use GDC to create two types of work queue: serial and concurrent.

func log(message: String) {
  let thread = Thread.current.isMainThread ? "Main" : "Background"
  print("\(thread) thread: \(message).")
}

func addNumbers(upTo range: Int) -> Int {
  log(message: "Adding numbers...")
  return (1...range).reduce(0, +)
}

let queue = DispatchQueue(label: "queue")

func execute<Result>(backgroundWork: @escaping () -> Result, mainWork: @escaping (Result) -> ()) {
  queue.async {
    let result = backgroundWork()
    DispatchQueue.main.async { mainWork(result) }
  }
}

execute(backgroundWork: { addNumbers(upTo: 100) },
        mainWork:       { log(message: "The sum is \($0)") })

To capture errors thrown by the asynchronous functions, you need to use the Result type:

enum Result<Success, Failure> where Failure: Error {
  case success(Success)
  case failure(Failure)
}

Sample code to return this Result:

struct Tutorial {
  let title: String
  let author: String
}

enum TutorialError: Error {
  case rejected
}

func feedback(for tutorial: Tutorial) -> Result<String, TutorialError> {
  Bool.random() ? .success("published") : .failure(.rejected)
}

Sample code to call this function:

func edit(_ tutorial: Tutorial) {
  queue.async {
    let result = feedback(for: tutorial)
    DispatchQueue.main.async {
      switch result {
        case let .success(data):
          print("\(tutorial.title) by \(tutorial.author) was \(data) on the website.")
        case let .failure(error):
          print("\(tutorial.title) by \(tutorial.author) was \(error).")
      }
    }
  }
}

let tutorial = Tutorial(title: "What’s new in Swift 5.1", author: "Cosmin Pupăză")
edit(tutorial)

How to use the Result in synchronous code:

let result = feedback(for: tutorial)
do {
  let data = try result.get()
  print("\(tutorial.title) by \(tutorial.author) was \(data) on the website.")
} catch {
  print("\(tutorial.title) by \(tutorial.author) was \(error).")
}

Chapter 22: Encoding & Decoding Types

Encodable and Decodable protocols

Encodable protocol:

func encode(to: Encoder) throws

Decodable protocol:

init(from decoder: Decoder) throws

Codable is something both Encodable and Decodable:

typealias Codable = Encodable & Decodable

If you have a struct whose fields are all codable, then it's enough to make it to conform to Codable:

struct Employee: Codable {
  var name: String
  var id: Int
}

If your struct has some custom fields, it's enough to make them too conformant to Codable:

struct Employee: Codable {
  var name: String
  var id: Int
  var favoriteToy: Toy?
}

struct Toy: Codable {
  var name: String
}

All collection types, including array and dictionary, of Codable are also automatically Codable.

JSONEncoder and JSONDecoder

If you have custom types to be converted to JSON:

let toy1 = Toy(name: "Teddy Bear");
let employee1 = Employee(name: "John Appleseed", id: 7, favoriteToy: toy1)

let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(employee1)
print(jsonData)

let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString) // {"name":"John Appleseed","id":7,"favoriteToy":{"name":"Teddy Bear"}}

If you want to decode JSON back to an instance:

let jsonDecoder = JSONDecoder()
let employee2 = try jsonDecoder.decode(Employee.self, from: jsonData)

CodingKey protocol and CodingKey enum

The CodingKeys enum, which confirms to the CodingKey protocol, lets you rename specific properties when doing serialization:

struct Employee: Codable {
  var name: String
  var id: Int
  var favoriteToy: Toy?
  
  enum CodingKeys: String, CodingKey {
    case id = "employeeId"
    case name
    case favoriteToy
  }
}
// { "employeeId": 7, "name": "John Appleseed", "favoriteToy": {"name": "Teddy Bear"}}

The encode function

In case you need to write a custom encoder, you need to override the encode and decode functions:

enum CodingKeys: String, CodingKey {
  case id = "employeeId"
  case name
  case gift
}

extension Employee: Encodable {
  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(name, forKey: .name)
    try container.encode(id, forKey: .id)
    try container.encode(favoriteToy?.name, forKey: .gift)
  }
}

extension Employee: Decodable {
  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    name = try values.decode(String.self, forKey: .name)
    id = try values.decode(Int.self, forKey: .id)
    if let gift = try values.decode(String?.self, forKey: .gift) {
      favoriteToy = Toy(name: gift)
    }
  }
}
>> {"name":"John Appleseed","gift":null,"employeeId":7}

encodeIfPresent and decodeIfPresent

encodeIfPresent and decodeIfPresent are used to skip null values:

extension Employee: Encodable {
  func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(name, forKey: .name)
    try container.encode(id, forKey: .id)
    try container.encodeIfPresent(favoriteToy?.name, forKey: .gift)
  }
}

extension Employee: Decodable {
  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    name = try values.decode(String.self, forKey: .name)
    id = try values.decode(Int.self, forKey: .id)
    if let gift = try values.decodeIfPresent(String.self, forKey: .gift) {
      favoriteToy = Toy(name: gift)
    }
  }
}

Writing tests for the Encode and Decoder

To check that encoder and decoder are in sync:

import XCTest

class EncoderDecoderTests: XCTestCase {
  var jsonEncoder: JSONEncoder!
  var jsonDecoder: JSONDecoder!
  var toy1: Toy!
  var employee1: Employee!
  
  override func setUp() {
    super.setUp()
    jsonEncoder = JSONEncoder()
    jsonDecoder = JSONDecoder()
    toy1 = Toy(name: "Teddy Bear")
    employee1 = Employee(name: "John Appleseed", id: 7, favoriteToy: toy1)
  }
  
  func testEncoder() {
    let jsonData = try? jsonEncoder.encode(employee1)
    XCTAssertNotNil(jsonData, "Encoding failed")
    
    let jsonString = String(data: jsonData!, encoding: .utf8)!
    XCTAssertEqual(jsonString, "{\"name\":\"John Appleseed\", \"gift\":\"Teddy Bear\",\"employeeId\":7}")
  }
  
  func testDecoder() {
    let jsonData = try! jsonEncoder.encode(employee1)
    let employee2 = try? jsonDecoder.decode(Employee.self, from: jsonData)
    XCTAssertNotNil(employee2)
    
    XCTAssertEqual(employee1.name, employee2!.name)
    XCTAssertEqual(employee1.id, employee2!.id)
    XCTAssertEqual(employee1.favoriteToy?.name, employee2!.favoriteToy?.name)
  }
}

EncoderDecoderTests.defaultTestSuite.run()

Chapter 23: Memory Management

Reference cycles for classes

A memory leak comes from two instances referencing each other.

Weak references

Weak references don't count on object references and automatically detect when the other instance is gone. Because of this, they always must be declared as Optional.

weak var editor: Editor?

Unowned references

Unowned references are like weak, i.e. they don't change the reference count, with the difference that they can never be null:

unowned let author: Author

Reference cycles for closures

Closures capture the context of the enclosing scope.
For example, adding this property:

lazy var description: () -> String = {
  "\(self.title) by \(self.author.name)"
}

The property is lazy so evaluated during the first use, so you create a strong reference cycle between Self and the closure.

Capture lists

A capture lists allows to control how you want the closure to be bound to the objects it refers to.

counter = 0
f = { [c = counter] in print(c) }
counter = 1
f()

Shorthand to create a local variable counter shadowing the counter of the bound object:

counter = 0
f = { [counter] in print(counter) }
counter = 1
f()

Unowned self

It's possible to break the reference cycle with an unowned self using a capture list:

lazy var description: () -> String = {
  [unowned self] in "\(self.title) by \(self.author.name)"
}

Weak self

If you can't capture Self because it can become nil, you can use a weak self.
For example, the following code breaks, because you deallocate tutorial and author at the end of the loop:

let tutorialDescription: () -> String
do {
  let author = Author(name: "Cosmin")
  let tutorial = Tutorial(title: "Memory management", author: author)
  tutorialDescription = tutorial.description
}
print(tutorialDescription())

To fix this:

lazy var description: () -> String = {
  [weak self] in "\(self?.title) by \(self?.author.name)"
}

Weak self won't extend the lifetime of self. If the underlying self goes away, it gets set to nil.

The strong-weak pattern

The strong-weak pattern converts the weak reference to a strong one:

lazy var description: () -> String = {
  [weak self] in guard let self = self else {
    return "The tutorial is no longer available."
  }
  return "\(self.title) by \(self.author.name)"
}

Chapter 24: Value Types & Value Semantics

Structs and enums are value types.
Classes and functions are reference types.
Arrays of value types implement value semantic.
Reference types defined as immutable behave like values types.

Defining value semantics

With value semantics, to change the value of a variable, you must hold a reference to that variable.

Copy-on-write on the rescue

How to implement copy-on-write:

struct PaintingPlan { // a value type, containing ...
  // a value type
  var accent = Color.white
  // a private reference type, for "deep storage"
  private var bucket = Bucket()
  
  // a pseudo-value type, using the deep storage
  var bucketColor: Color {
    get {
      bucket.color
    }
    set {
      bucket = Bucket(color: newValue)
    }
  }
}

Optimization:

struct PaintingPlan { // a value type, containing ...
  // ... as above ...
  
  // a computed property facade over deep storage
  // with copy-on-write and in-place mutation when possible
  var bucketColor: Color {
    get {
      bucket.color
    }
    set {
      if isKnownUniquelyReferenced(&bucket) {
        bucket.color = bucketColor
      } else {
        bucket = Bucket(color: newValue)
      }
    }
  }
}

Sidebar: property wrappers

As the copy-on-write pattern is verbose, you can use a property wrapper to make it more concise:

struct PaintingPlan {

  var accent = Color.white
  @CopyOnWriteColor var bucketColor = .blue
}

The CopyOnWriteColor is expanded by the compiler into:

private var _bucketColor = CopyOnWriteColor(wrappedValue: .blue)

var bucketColor: Color {
  get { _bucketColor.wrappedValue }
  set { _bucketColor.wrappedValue = newValue }
}

And here is the definition of CopyOnWriteColor:

@propertyWrapper
struct CopyOnWriteColor {

  init(wrappedValue: Color) {
    self.bucket = Bucket(color: wrappedValue)
  }
  
  private var bucket: Bucket
  
  var wrappedValue: Color {
    get {
      bucket.color
    }
    set {
      if isKnownUniquelyReferenced(&bucket) {
        bucket.color = newValue
      } else {
        bucket = Bucket(color:newValue)
      }
    }
  }
}

Chapter 25: Protocol-Oriented Programming

Introducing protocol extensions

Already seen how to implement a protocol via an extension:

protocol TeamRecord {
  var wins: Int { get }
  var losses: Int { get }
  var winningPercentage: Double { get }
}

extension TeamRecord {
  var gamesPlayed: Int {
    wins + losses
  }
}

The extension includes the implementation of the method.

Now it's possible to define a class confirming to the protocol, and also use the extension method:

struct BaseballRecord: TeamRecord {
  var wins: Int
  var losses: Int
  
  var winningPercentage: Double {
    Double(wins) / Double(wins + losses)
  }
}

let sanFranciscoSwifts = BaseballRecord(wins: 10, losses: 5)
sanFranciscoSwifts.gamesPlayed // 15

Default implementations

To avoid the need of implement a method in every class and struct confirming to a protocol, you can define a default implementation in the protocol itself:

extension TeamRecord {
  var winningPercentage: Double {
    Double(wins) / Double(wins + losses)
  }
}

Clearly a class or struct can reimplement this method if needed.

Understanding protocol extension dispatch

Note: if a type defines a method or property in a protocol extension, without declaring it in the protocol itself, dispatch is static, i.e. it considers the static type of the variable and not its actual instance type.

protocol WinLoss {
  var wins: Int { get }
  var losses: Int { get }
}

extension WinLoss {
  var winningPercentage: Double {
    Double(wins) / Double(wins + losses)
  }
}

struct CricketRecord: WinLoss {
  var wins: Int
  var losses: Int
  var draws: Int
  
  var winningPercentage: Double {
    Double(wins) / Double(wins + losses + draws)
  }
}

let miamiTuples = CricketRecord(wins: 8, losses: 7, draws: 1)
let winLoss: WinLoss = miamiTuples

miamiTuples.winningPercentage // 0.5
winLoss.winningPercentage // 0.53 !!!

Type constraints

With type constraints on a protocol extension, you can call properties and methods from another type inside the extension itself:

protocol PostSeasonEligible {
  var minimumWinsForPlayoffs: Int { get }
}

extension TeamRecord where Self: PostSeasonEligible {
  var isPlayoffEligible: Bool {
    wins > minimumWinsForPlayoffs
  }
}

Note that the constraint could be another protocol itself.

Protocol-oriented benefits

  • Program to interfaces, not implementations
    In particular, you can program against types that don't support inheritance
  • Traits, mixins and multiple inheritance
    Your types can also support multiple protocols, so have some sort of multiple inheritance
  • Simplicity
    Use default implementations to keep code in one place

Why Swift is a protocol-oriented language

There aren't many classes in the Swift class library, because it uses many structs. For example, Array and Dictionary are structs.
They can be extended with protocols, that can apply to both Array and Dictionary. In an OOP language, Array and Dictionary should have been based on the same base class, while with Swift, this is not necessary.

Chapter 26: Advanced Protocols & Generics

Existential protocols

Existential type is a type implementing a protocol:

protocol Pet {
  var name: String { get }
}
struct Cat: Pet {
  var name: String
}

var somePet: Pet = Cat(name: "Whiskers")

Non-existential protocols

If a protocol has associated types, you can't use it as an existential type:

protocol Pet {
  associatedtype Food
  var name: String { get }
}

var somePet: Pet = Cat(name: "Whiskers")
// Now you get a compile time error

Constraining the protocol to a specific type

You can add constraints to associated types:

protocol WeightCalculatable {
  associatedtype WeightType: Numeric
  var weight: WeightType { get }
}

Expressing relationships between types

Sample of mapping protocols and concrete types with the factory pattern:

protocol Product {
  init()
}

protocol ProductionLine {
  associatedtype ProductType
  func produce() -> ProductType
}

protocol Factory {
  associatedtype ProductType
  func produce() -> [ProductType]
}

So new we can define concrete types:

struct GenericProductionLine<P: Product>: ProductionLine {
  func produce() -> P {
    P()
  }
}

struct GenericFactory<P: Product>: Factory {
  var productionLines: [GenericProductionLine<P>] = []
  
  func produce() -> [P] {
    var newItems: [P] = []
    productionLines.forEach { newItems.append($0.produce()) }
    print("Finished Production")
    print("-------------------")
    return newItems
  }
}

Final usage of the concrete types:

var carFactory = GenericFactory<Car>()
carFactory.productionLines = [GenericProductionLine<Car>(), GenericProductionLine<Car>()]
carFactory.produce()

Type erasure

Type erasure is a technique to erase type information that is not important:

let array = Array(1...10)
let set = Set(1...10)
let reversedArray = array.reversed()

let arrayCollection = [array, Array(set), Array(reversedArray)  // [[Int]]

let collections = [AnyCollection(array),
                   AnyCollection(set),
                   AnyCollection(array.reversed())]

let total = collections.flatMap { $0 }.reduce(0, +)  // 165

Opaque return types

Opaque return types allow not to create an Any*** wrapper type.
This feature allows to have a function that returns a protocol, for example:

func makeValue() -> some FixedWithInteger {
  42
}

The function returns a compiler-known well defined type. For example it's not possible in one if branch to return Int(42) and in the else Int8(24): both cases must return always the same type.

It's also possible to return a composition of protocols:

func makeEquatableNumericInt() -> some Numeric & Equatable { 1 }
func makeEquatableNumericDouble() -> some Numeric & Equatable { 1.0 }

let value1 = makeEquatableNumericInt()
let value2 = makeEquatableNumericInt()

print(value1 == value2)  // true
print(value1 + value2)   // 2
print(value1 > value2)   // error: it misses Comparable

Recursive protocols

A protocol can be recursive, for example:

protocol GraphNode {
  var connectedNodes: [GraphNode] { get set }
}

If instead you want to define a Matrioska class, with constrained Self:

protocol Matryoshka: AnyObject {
  var inside: Self? { get set }
}

final class HandCraftedMatryoshka: Matryoshka {
  var inside: HandCraftedMatryoshka?
}

final class MachineCraftedMatryoshka: Matryoshka {
  var inside: MachineCraftedMatryoshka?
}

Heterogeneous collections

If you want to have an array of different types:

protocol WeightCalculatable {
  associatedtype WeightType: Numeric
  var weight: WeightType { get }
}

var array1: [WeightCalculatable] = [] // compile error
var array2: [HeavyThing] = []
var array3: [LightThing] = []

Xcode will suggest to declare the array as [Any].

Type erasure

Using Any[] like before is not good, because too generic.
You can list all the types in the array declaration, for example:

class AnyHeavyThing<T: Numeric>: WeightCalculatable {
  var weight: T {
    123
  }
}

class HeavyThing2: AnyHeavyThing<Int> {
  override var weight: Int {
    100
  }
}

class VeryHeavyThing2: AnyHeavyThing<Int> {
  override var weight: Int {
    9001
  }
}

var heavyList2 = [HeavyThing2(), VeryHeavyThing2()]
heavyList2.forEach { print($0.weight) }

Opaque return types

In case you want to hide details from the Factory example before:

func makeFactory() -> Factory { // compile error
  GenericFactory<Car>()
}
let myFactory = makeFactory()

But this generates a compile time error. But it can be fixed:

func makeFactory() -> some Factory { // compiles!
  GenericFactory<Car>()
}

Practically, the compiler knows that it's a GenericFactory< Car>, but it exposes it as a Factory protocol.
In fact, the following code doesn't compile because the compiler must know the type:

func makeFactory(isChocolate: Bool) -> some Factory {
  if isChocolate {
    return GenericFactory<Chocolate>()
  } else {
    return GenericFactory<Car>()
  }
}