Chapters

Hide chapters

Swift Apprentice: Fundamentals

First Edition · iOS 16 · Swift 5.7 · Xcode 14.2

Section III: Building Your Own Types

Section 3: 9 chapters
Show chapters Hide chapters

6. Optionals
Written by Matt Galloway

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

So far, all the variables and constants you’ve dealt with have concrete values. When you had a string variable, like var name, it had a string value associated with it, like "Matt Galloway". It could have been an empty string, like "", but there was a value to which you could refer.

That’s one of the built-in safety features of Swift: If the type says Int or String, then there’s an actual integer or string there, guaranteed.

This chapter will introduce you to the concept of optionals, a special Swift type that can represent a value and the absence of that value. By the end of this chapter, you’ll know why you need optionals and how to use them safely.

Introducing nil

Sometimes, it’s necessary to represent the absence of a value. Imagine a scenario where you need to refer to a person’s identifying information; you want to store the person’s name, age and occupation. Name and age are both things that must have a value — everyone has them. But not everyone is employed, so the absence of a value for occupation is something you need to handle.

Without knowing about optionals, this is how you might represent the person’s name, age and occupation:

var name = "Matt Galloway"
var age = 30
var occupation = "Software Developer & Author"

But what if I become unemployed? Maybe I’ve won the lottery and want to give up work altogether (I wish!). This is when it would be useful to be able to refer to the absence of a value.

Why couldn’t you just use an empty string? You could, but optionals are a much better solution. Read on to see why.

Sentinel Values

A value representing a special condition, such as the absence of a value, is known as a sentinel value or a special value. That’s what your empty string would be in the previous example.

var errorCode = 0

Introducing Optionals

Optionals are Swift’s solution to the problem of representing both a value and the absence of a value. An optional can hold either a value or nil.

7 Uwpuadoh wij cefsiayawf e cibei Omteoteq fax toqjeawuyy cu wojoi

var errorCode: Int?
errorCode = 100
errorCode = nil
781 awnodFipa = aklicZuwa =

Mini-Exercises

  1. Make an optional String called myFavoriteSong. If you have a favorite song, set it to a string representing that song. If you have more than one favorite song or no favorite, set the optional to nil.
  2. Create a constant called parsedInt and set it equal to Int("10"), which tries to parse the string 10 and convert it to an Int. Check the type of parsedInt using Option-Click. Why is it an optional?
  3. Change the string being parsed in the above exercise to a non-integer (try dog, for example). What does parsedInt equal now?

Unwrapping Optionals

It’s all well and good that optionals exist, but you may be wondering how you can look inside the box and manipulate the value it contains.

var result: Int? = 30
print(result)
print(result + 1)

Force Unwrapping

The error message indicates the solution: It tells you that the optional must be unwrapped. You need to unwrap the value from its box. It’s like Christmas!

var authorName: String? = "Matt Galloway"
var authorAge: Int? = 30
var unwrappedAuthorName = authorName!
print("Author is \(unwrappedAuthorName)")
authorName = nil
print("Author is \(authorName!)")
if authorName != nil {
  print("Author is \(authorName!)")
} else {
  print("No author.")
}

Optional Binding

Swift includes a feature known as optional binding, which lets you safely access the value inside an optional. You use it like so:

if let unwrappedAuthorName = authorName {
  print("Author is \(unwrappedAuthorName)")
} else {
  print("No author.")
}
if let authorName = authorName {
  print("Author is \(authorName)")
} else {
  print("No author.")
}
if let authorName = authorName,
   let authorAge = authorAge {
  print("The author is \(authorName) who is \(authorAge) years old.")
} else {
  print("No author or no age.")
}
if let authorName = authorName,
   let authorAge = authorAge,
   authorAge >= 40 {
  print("The author is \(authorName) who is \(authorAge) years old.")
} else {
  print("No author or no age or age less than 40.")
}

Shorthand

You will have noticed in the examples above that you can end up repeating yourself a lot when using optional binding. Take the following example:

if let authorName = authorName {
  print("The author is \(authorName)")
}
if let authorName {
  print("The author is \(authorName)")
}
if let authorName, let authorAge {
  print("The author is \(authorName) who is \(authorAge) years old.")
}

Mini-Exercises

  1. Using your myFavoriteSong variable from earlier, use optional binding to check if it contains a value. If it does, print out the value. If it doesn’t, print "I don’t have a favorite song."
  2. Change myFavoriteSong to the opposite of what it is now. If it’s nil, set it to a string; if it’s a string, set it to nil. Observe how your printed result changes.

Introducing guard

Sometimes you want to check a condition and only continue executing a function if the condition is true, such as when you use optionals. Imagine a function that fetches some data from the network. That fetch might fail if the network is down. The usual way to encapsulate this behavior is using an optional, which has a value if the fetch succeeds, and nil otherwise.

func guardMyCastle(name: String?) {
  guard let castleName = name else {
    print("No castle!")
    return
  }

  // At this point, `castleName` is a non-optional String

  print("Your castle called \(castleName) was guarded!")
}
func calculateNumberOfSides(shape: String) -> Int? {
  switch shape {
  case "Triangle":
    return 3
  case "Square":
    return 4
  case "Rectangle":
    return 4
  case "Pentagon":
    return 5
  case "Hexagon":
    return 6
  default:
    return nil
  }
}
func maybePrintSides(shape: String) {
  let sides = calculateNumberOfSides(shape: shape)

  if let sides = sides {
    print("A \(shape) has \(sides) sides.")
  } else {
    print("I don’t know the number of sides for \(shape).")
  }
}
func maybePrintSides(shape: String) {
  guard let sides = calculateNumberOfSides(shape: shape) else {
    print("I don’t know the number of sides for \(shape).")
    return
  }

  print("A \(shape) has \(sides) sides.")
}

Nil Coalescing

There’s a rather handy alternative way to unwrap an optional. You use it when you want to get a value out of the optional no matter what — and in the case of nil, you’ll use a default value. This operation is called nil coalescing. Here’s how it works:

var optionalInt: Int? = 10
var mustHaveResult = optionalInt ?? 0
var optionalInt: Int? = 10
var mustHaveResult: Int
if let unwrapped = optionalInt {
  mustHaveResult = unwrapped
} else {
  mustHaveResult = 0
}
optionalInt = nil
mustHaveResult = optionalInt ?? 0

Challenges

Before moving on, here are some challenges to test your knowledge of optionals. It is best to try to solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.

Challenge 1: You Be the Compiler

Which of the following are valid statements?

var name: String? = "Ray"
var age: Int = nil
let distance: Float = 26.7
var middleName: String? = nil

Challenge 2: Divide and Conquer

First, create a function that returns the number of times an integer can be divided by another integer without a remainder. The function should return nil if the division doesn’t produce a whole number. Name the function divideIfWhole.

func divideIfWhole(_ value: Int, by divisor: Int)

Challenge 3: Refactor and Reduce

The code you wrote in the last challenge used if statements. In this challenge, refactor that code to use nil coalescing instead. This time, make it print "It divides X times" in all cases, but if the division doesn’t result in a whole number, then X should be 0.

Challenge 4: Nested Optionals

Consider the following nested optional — it corresponds to a number inside a box inside a box inside a box.

let number: Int??? = 10
print(number)
// Optional(Optional(Optional(10)))

print(number!)
// Optional(Optional(10))

Key Points

  • nil represents the absence of a value.
  • Non-optional variables and constants are never nil.
  • Optional variables and constants are like boxes that can contain a value or be empty (nil).
  • To work with the value inside an optional, you must first unwrap it from the optional.
  • The safest way to unwrap an optional’s value is by using optional binding or nil coalescing. Use forced unwrapping only when appropriate, as it could produce a runtime error.
  • You can guard let to bind an optional. If the binding fails, the compiler forces you to exit the current function (or halt execution). This construction guarantees that your program never executes with an uninitialized value.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now