Skip to content

Wrapping Up

This chapter wraps up some loose ends and completes the topics for this course. These topics relate to earlier chapters but were postponed because they were too advanced or distracted from the main discussion of the chapter. Nevertheless, the topics are important enough to deserve a place in this course, which is why they’re included here.

Control flow

This section continues the discussion on control flow from Control Flow and Booleans and Advanced Control Flow. In this section, you’ll learn how to:

  • break or continue a specific statement using statement labels.
  • Execute multiple switch cases using the fallthrough statement.

Labeled statements

The following example shows some of the control flow statements in the doPlayerTurn function from Blackjack:

swift
repeat {  // for all hands
  if currentHand > 0 {
    if playerHands[currentHand].first!.rank == "A" {
      continue  // with the next hand
    }
  }
  while value(for: playerHands[currentHand]).value < 21 {
    if answer == "s" {
      break  // no more actions for this hand
    }
    if answer == "p" {
      if playerHands[currentHand][0].rank == "A" {
        break  // no more actions for this hand
      }
    }
    if answer == "d" {
      break  // no more actions for this hand
    }
  }
} while currentHand < playerHands.count && !playerHands[currentHand].isEmpty

This code has a while loop nested in a repeat-while loop. Both loops contain nested if statements and use break or continue for additional control flow.

Unfortunately, this nested control flow obscures the relationship between the break and continue statements, and the loops they affect.

You can improve this code by labeling the loop statements. With labels, you can be specific about which statement you want to break or continue:

swift
hands: repeat {
  if currentHand > 0 {
    if playerHands[currentHand].first!.rank == "A" {
      continue hands
    }
  }
  actions: while value(for: playerHands[currentHand]).value < 21 {
    if answer == "s" {
      break actions
    }
    if answer == "p" {
      if playerHands[currentHand][0].rank == "A" {
        break actions
      }
    }
    if answer == "d" {
      break actions
    }
  }
} while currentHand < playerHands.count && !playerHands[currentHand].isEmpty

Labels act as a form of documentation and improve the clarity of your code. They also enable otherwise impossible control flow, such as breaking a loop from inside a nested loop or switch statement:

swift
enum Input {

  case play
  case quit
}

func readInput() -> Input {
  // ...
}

menu: while true {
  switch readInput() {
  case .play:
    // ...
  case .quit:
    break menu
  }
}

fallthrough

The fallthrough statement causes control to “fall through” from one switch case into the next. It ignores the next case’s condition and executes it as if it was part of the current case.

The following example uses fallthrough to include the benefits of a bronze membership in a silver membership, and those of a silver membership in a gold membership:

swift
enum Benefit {
  
  case priorityBoarding
  case seatSelection
  case discount
  case firstClass
}

enum Membership {
  
  case gold
  case silver
  case bronze
  
  var benefits: [Benefit] {
    var result: [Benefit] = []
    switch self {
    case .gold:
      result += [.firstClass]
      fallthrough
    case .silver:
      result += [.discount]
      fallthrough
    case .bronze:
      result += [.priorityBoarding, .seatSelection]
    }
    return result
  }
}

Functions

This section continues the discussion on functions from Functions and Function Types and Closures. In this section, you’ll learn:

  • How you can pass any number of arguments to a function using a variadic parameter.
  • How a function can mutate its arguments using in-out parameters.
  • How you can improve encapsulation by nesting functions.
  • That classes aren’t the only reference types in Swift.

Variadic parameters

A variadic parameter accepts any number of arguments of a single type. To declare a variadic parameter, you follow its type with three dots (...).

In the following example, the receive(_:) method has a variadic parameter that accepts any number of cards:

swift
struct Hand {
  
  private(set) var cards: [Card] = []
  
  mutating func receive(_ cards: Card...) {
    // ...
  }
}

You can call this method with a single argument:

swift
var hand = Hand()
hand.receive(Card(rank: .ace, suit: .hearts))

With multiple arguments:

swift
hand.receive(
  Card(rank: .two, suit: .spades),
  Card(rank: .three, suit: .diamonds)
)

And even with zero arguments:

swift
hand.receive()

A variadic parameter collects its arguments in an array. In the body of receive(_:), the type of cards is [Card]:

swift
mutating func receive(_ cards: Card...) {
  for card in cards {
    self.cards.append(card)
  }
}

A variadic parameter is useful when you don’t know ahead of time how many arguments a caller will need to pass. Without a variadic parameter, you’d have to use an array and wrap the arguments in square brackets:

swift
hand.receive([
  Card(rank: .two, suit: .spades),
  Card(rank: .three, suit: .diamonds)
])

A variadic parameter is more convenient because it creates the array for you. However, if you want to pass an existing array to a function, you need an array parameter:

swift
mutating func receive(_ cards: [Card]) {
  self.cards.append(contentsOf: cards)
}

In-out parameters

As you already know from Functions, function parameters are constants. When you call a function, you assign arguments to the function’s parameters, which are constants in the function’s body.

In-out parameters are different. An in-out parameter is a variable in the function body. You can change its value, and when the function ends, it copies the value of the in-out parameter back to its argument, which must also be a variable.

For example:

swift
func increment(_ number: inout Int) {
  number += 1
}

var x = 0
increment(&x)

In this example, number is an in-out parameter, as indicated by the keyword inout before its type. The ampersand (&) before the argument x is required and reminds you that x is being passed in-out, and that the function will mutate its value. When increment returns, it assigns the incremented value of number back to x.

In-out parameters can improve the performance of your code. When you use an in-out parameter, the compiler can configure this parameter as a reference to its argument. This removes the overhead of copying large values, such as arrays, into and out of a function.

The Standard Library includes a reduce method that uses an in-out parameter for just this reason. Here’s how you use this method to remove the duplicates from an array of integers:

swift
var values = [1, 2, 3, 1]
values = values.reduce(into: []) {
  (result: inout [Int], next: Int) in
  if !result.contains(next) {
    result.append(next)
  }
}

This reduction starts with an empty array. The trailing closure processes an element of the original array and appends it to result if it’s not a duplicate. Because result is an in-out parameter, reduce can pass the same array to every reduction step, without having to create a new copy every time.

Nested functions

In Programming with Structures, you learned that you can reduce the complexity of your code by using the private keyword to hide a type’s internals from other types.

Another way you can improve encapsulation is by nesting functions. You can declare a function inside the body of another function. This nested function is only usable by its outer function and hidden from other functions.

As an example, consider the print method from the solution to Enumerations:

swift
func print() {
  Swift.print("""
    \(value(at: 0)) | \(value(at: 1)) | \(value(at: 2))
    -----------
    \(value(at: 3)) | \(value(at: 4)) | \(value(at: 5))
    -----------
    \(value(at: 6)) | \(value(at: 7)) | \(value(at: 8))
    """)
}

This method depends on another method, value(at:), which returns either the raw value for the player at a given position, or a default value:

swift
private func value(at position: Int) -> String {
  if let player = positions[position] {
    player.rawValue
  } else {
    "\(position + 1)"
  }
}

You only use value(at:) in print, so you can make it a nested method:

swift
func print() {
  func value(at position: Int) -> String {
    if let player = positions[position] {
      player.rawValue
    } else {
      "\(position + 1)"
    }
  }

  Swift.print("""
    \(value(at: 0)) | \(value(at: 1)) | \(value(at: 2))
    -----------
    \(value(at: 3)) | \(value(at: 4)) | \(value(at: 5))
    -----------
    \(value(at: 6)) | \(value(at: 7)) | \(value(at: 8))
    """)
}

This clearly conveys the purpose of value(at:) as a helper method for print and hides it from other functions.

Functions are reference types

In Function Types and Closures, you learned that functions have a type and that you can use these types to build higher-order functions. Here’s an example from that chapter:

swift
func makeCounter() -> () -> Int {
  var count = 0
  return {
    count += 1
    return count
  }
}

In this example, makeCounter returns a closure that captures its local variable count. This closure works like a counter. Every time you call it, it increments and returns its count.

Every call to makeCounter creates a new local variable and returns a new closure. This means all counters operate independently:

swift
var c1 = makeCounter()
var c2 = makeCounter()
c1()
c2()

Now, consider the following example:

swift
var c1 = makeCounter()
var c2 = makeCounter()
var c3 = c2
c1()
c2()
c3()

Unsurprisingly, the calls c1() and c2() return 1. However, the call c3() returns 2. This is because functions are reference types.

c1 and c2 each hold a reference to a function, and the assignment var c3 = c2 copies the reference in c2 to c3. The call c3() returns 2 because it’s the second call to the function that both c2 and c3 refer to.

Optionals

This section continues the discussion on Optionals. In this section, you’ll learn:

  • How you can access properties, methods, and subscripts of an optional value using optional chaining.
  • That some optionals are implicitly unwrapped when you use them.

Optional chaining

Consider the following example:

swift
struct Address {
  
  var province: String?
}

class User {
  
  var address: Address?
}

var authenticatedUser: User?

In this example, there may be an authenticated user, who may have specified their address, which may include a province. This means you have to unwrap three optionals to find the user’s province:

swift
if let user = authenticatedUser {
  if let address = user.address {
    if let province = address.province {
      // ...
    }
  }
}

This construction of nested if statements is known as a pyramid of doom.

Optional chaining can save you from this doom. It’s a quick and safe way to access a property, method, or subscript of an optional value.

Here’s how you find the user’s province with an optional chain:

swift
if let province = authenticatedUser?.address?.province {
  // ...
}

You start an optional chain by adding a question mark (?) after an optional value. You can then access a property, method, or subscript of that optional as if it were unwrapped. If the property, method, or subscript also returns an optional, you can add another question mark and continue the chain.

An optional chain returns either the expected value — in this case, the province — or nil if any optional in the chain was nil. This means the type of authenticatedUser?.address?.province is String?.

As you can see, optional chaining doesn’t fully unwrap an optional. The value of an optional chain is still an optional. However, the chain does flatten multiple optionals into a single optional, so you have to unwrap only one optional, not three.

An optional chain can also be the target of an assignment:

swift
authenticatedUser?.address?.province = "OVL"

This assignment only happens if neither authenticatedUser nor address are nil.

Another example where optional chaining is useful is the value(at:) method from before:

swift
func value(at position: Int) -> String {
  if let player = positions[position] {
    player.rawValue
  } else {
    "\(position + 1)"
  }
}

You can also implement this method as follows:

swift
func value(at position: Int) -> String {
  positions[position]?.rawValue ?? "\(position + 1)"
}

This code combines optional chaining with the nil-coalescing operator (??) to safely read the rawValue property and fall back on a default value when the position is empty.

Implicitly unwrapped optionals

Earlier, you declared the following variable:

swift
var authenticatedUser: User?

Now, suppose your application includes a screen where this user can configure their profile:

swift
class UserProfileScreen {
  
  private var user: User?
  
  init() {
    user = nil
  }

  func display(for user: User) {
    self.user = user
    // ...
  }

  func save(_ address: Address) {
    user!.address = address
  }
}

Most likely, you create an instance of this class when the application starts. However, at that point, the user hasn’t authenticated yet, so the user property is nil.

After the user authenticates, they can access the profile screen. When this happens, you call the display(for:) method to set the user property and display the screen.

In this example, user isn’t really an optional. You set it when you call display(for:), and it never returns to nil. All code that uses user — such as save — can safely assume it’s not nil and force unwrap it. The only reason why user is an optional is that its value isn’t available when you initialize the screen.

Implicitly unwrapped optionals support cases like this. You declare an implicitly unwrapped optional with an exclamation point (!) instead of a question mark:

swift
private var user: User!

As their name implies, the compiler implicitly unwraps these optionals for you:

swift
func save(_ address: Address) {
  user.address = address
}

Here, the compiler adds an implicit exclamation point after user. The exclamation point in the declaration of user reminds you of this behavior. It also reminds you that implicit force unwraps are just as dangerous as explicit ones.

Implicitly unwrapped optionals should only be nil during initialization, never when you use them. In all other cases, you should use a regular optional and unwrap it safely.

Types

This section continues the discussion on types from Structures, Enumerations, and Classes. In this section, you’ll learn that:

  • You can nest types to improve encapsulation and avoid name conflicts.
  • You can react to changes in properties using property observers.
  • Enumeration cases can store additional information using associated values.
  • Associated values can lead to recursive enumerations.

Nested types

In Enumerations, you declared types Rank and Suit to represent the rank and suit of a card. You can make the relationship between these types explicit by nesting Rank and Suit inside Card:

swift
struct Card {

  enum Rank {
    // ...
  }

  enum Suit {
    // ...
  }

  // ...
}

Unlike functions, nested types aren’t hidden by default. Outside of Card, you refer to Rank and Suit as Card.Rank and Card.Suit. However, you can make these types private, in which case they’re only usable by Card.

Nested types also avoid name conflicts. Suppose that you want to create a type to represent a player’s experience level, and that you also want to name it Rank:

swift
enum Rank {

  case novice
  case intermediate
  case expert
}

Without nesting, the two Rank types will conflict, and the compiler will refuse your code. By nesting the types so that they become Card.Rank and Player.Rank, you can resolve this name conflict:

swift
class Player {

  enum Rank {

    case novice
    case intermediate
    case expert
  }

  // ...
}

Property observers

Consider the Image class from Classes:

swift
class Image {

  var width: Int
  var height: Int
  private(set) var pixels: [Color]

  // ...
}

A user of this class can set an image’s width and height, but not its pixels. It’s up to you, the developer of the class, to adjust the image when the user resizes it. For this, you can use property observers.

A property observer is a block of code that you attach to a property. The observer executes whenever the property is mutated.

Property observers come in two flavors:

  • A willSet observer runs immediately before the property is set. This observer has access to the value being set as newValue.
  • A didSet observer runs immediately after the property has been set. This observer has access to the value that was replaced as oldValue.

Here’s how a willSet observer can adjust the image when the user sets the height property:

swift
var height: Int {
  willSet {
    if newValue > height {
      let newRows = newValue - height
      pixels += Array(repeating: .black, count: width * newRows)
    } else {
      let deletedRows = height - newValue
      pixels.removeLast(width * deletedRows)
    }
  }
}

This observer either adds rows of black pixels or crops the image. You can achieve the same result with a didSet observer:

swift
var height: Int {
  didSet {
    if height > oldValue {
      let newRows = height - oldValue
      pixels += Array(repeating: .black, count: width * newRows)
    } else {
      let deletedRows = oldValue - height
      pixels.removeLast(width * deletedRows)
    }
  }
}

Observers aren’t active during initialization. You only use them to react to changes that happen after the instance has been initialized.

Enumerations with associated values

Suppose that you’re building an application that offers two authentication options: users either provide an email address and password credential, or they use a social network account. You can use an enumeration to model these options:

swift
enum Credential {
  
  case custom(email: String, password: String)
  case social(id: Int)
}

This enumeration has associated values that store additional information with each case. As you can see, each case can have a different set of zero or more associated values.

You must specify associated values when you instantiate a case that has them:

swift
var credential: Credential
credential = .social(id: 123)

Case social is meaningless without an associated id:

swift
credential = .social  

This is because social no longer represents a single value. Instead, it represents a category of values — one for each possible id.

Associated values aren’t properties. Although each instance stores its associated values, you cannot access them using dot syntax, nor can you change them.

You must use the value-binding pattern to read associated values:

swift
switch credential {
case .custom(let email, let password):
  print("Authenticated as \(email) with password \(password).")
case .social(let id):
  print("Authenticated as user #\(id).")
}

This pattern isn’t exclusive to the switch statement. Here’s how you use it with an if statement:

swift
if case .social(let id) = credential {
  print("Authenticated as user #\(id).")
}

Note

Associated values are incompatible with raw values. A single enumeration cannot use both.

Recursive enumerations

The type of an associated value can be any type, even the enumeration itself. This results in a recursive enumeration.

Here’s a recursive enumeration that models simple algebraic expressions:

swift
indirect enum Expression {
  
  case constant(Double)
  case addition(Expression, Expression)
  case subtraction(Expression, Expression)
  case multiplication(Expression, Expression)
  case division(Expression, Expression)

  var value: Double {
    switch self {
    case .constant(let value):
      value
    case let .addition(lhs, rhs):
      lhs.value + rhs.value
    case let .subtraction(lhs, rhs):
      lhs.value - rhs.value
    case let .multiplication(lhs, rhs):
      lhs.value * rhs.value
    case let .division(lhs, rhs):
      lhs.value / rhs.value
    }
  }
}

The keyword indirect marks this enumeration as recursive and is required to avoid an infinite loop. You add this keyword either to the enumeration as a whole or to the recursive cases only.

You can use type Expression to implement a simple calculator. The user can input an expression such as 1 + 2 * 3, and you can evaluate this expression as follows:

swift
let expr = Expression.addition(
  .constant(1),
  .multiplication(
    .constant(2),
    .constant(3)
  )
)
expr.value

Up next

This chapter completes the topics for this course. Up next is your final challenge, where you’ll be able to apply everything you’ve learned so far.