BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

< Back to Latest Articles

Understanding assertions

Assertions allow us to have Swift silently check the state of our program at runtime, but if you want to get them right you need to understand some intricacies. In this article I’ll walk you through the five ways we can make assertions in Swift, and provide clear advice on which to use and when.

Watch the video here, or read the article below

Quick links

assert()

The simplest possible assertion is simply assert(), which takes a condition to check and a message to print if that check fails. If the conditions fails, your app will crash and the message will be printed. Use it like this:

func sum(of numbers: [Int]) -> Int {
    assert(numbers.count > 0, "This should always be given some numbers to sum.")
    return numbers.reduce(0, +)
}

Any use of assert() only applies while you are debugging – it will be compiled out completely when you ship your app to the App Store, as if the whole assert() line never existed.

As a result, you should assert() frequently.

“But Paul,” I hear you say, “I already use assert().” No. Use it twice as much. Use it five times as much. Seriously, there is no good reason for not adding more assertions to your code: they literally have zero performance impact on your code, and just act as a way of checking that the assumptions you have about your program are valid.

So, add assertions at the start of your functions, checking the values are within the range you expect. Heck, add assertions in the middle of your functions if you want, again making sure everything is how you expect.

Remember, because it gets compiled out completely, don’t be afraid to make your condition slower than you would normally accept, like this:

assert(reallySlowFunction() == true, "Slow function returned an incorrect value")

If you ever find yourself reaching for print(), consider using assert() instead – rather than forcing you to read through your output to see what happened, assert() literally checks the thing you care about and won’t print anything if everything is what you expect.

Importantly, if you build with unchecked optimizations (setting Disable Safety Checks to Yes in your build settings), Swift is free to assume that your assertions are always true. This isn’t so dangerous here, but it can be in some of the other functions below.

TL;DR: Use assert() regularly; it costs nothing and will help you.

assertionFailure()

One step up from assert() is assertionFailure(), which no longer has a condition: it will always crash your app when the function is called, printing any message you provide. As with assert() this will only ever be used in debug builds, so you don’t have to worry about this in release mode.

Use it like this:

let number = Int.random(in: 1...3)

switch number {
case 1:
    print("You rolled a one!")
case 2:
    print("You rolled a two!")
case 3:
    print("You rolled a three!")
default:
    assertionFailure("Rolling a 3-sided dice shouldn't have any other results.")
}

If you build with unchecked optimizations, the compiler is free to assume that your assertionFailure() code is never called – it should be unreachable. This is not the same as a failed assertion: using assert() means “crash when this line is hit in debug mode if my test fails,” but using assertionFailure() means “this will always crash in debug mode,” so in theory the compiler doesn’t have to worry about any code following the call to assertionFailure().

That “in theory” matters: using assertionFailure() provides an optimization hint, but what Swift’s optimizer does here isn’t specifically defined so you’re in uncertain territory.

TL;DR: Use assertionFailure() for situations where you know your code won’t be reached, for example if you’ve written a switch statement and need to put something in default even though you know it won’t be reached because all other cases are covered.

precondition()

One step up from assertionFailure() is precondition(), which works similarly to assert() in that you provide it a condition to check and it will crash your code and print a message if the check fails.

Use it like this:

extension Array {
    mutating func removeRandom(_ number: Int) {
        precondition(count >= number, "Can't remove \(number) items from an array with count \(count)")

        for _ in 0..<number {
            remove(at: Int.random(in: 0..<count))
        }
    }
}

Unlike assertions, precondition() will not be compiled out of release builds, which means the check will take place on your users devices and will crash your code if it fails.

So, you should use preconditions where your checks are designed to preserve data integrity. For example, the Swift standard library uses preconditions to check that you aren’t trying to make a range where the upper bound is less than the lower bound.

If you build with unchecked optimizations, Swift is free to assume that your checks always return true, even in release mode.

TL;DR: Use precondition() when you need to be absolutely sure the state of your program is what you expect before continuing, even when running on user devices.

preconditionFailure()

Going a step up from precondition() we have preconditionFailure(), which is the counterpart to assertionFailure(): it doesn’t perform a test, but will unconditionally crash your code when reached.

Use it like this:

func updateStatus(to newStatus: String) -> String {
    if newStatus == "active" || newStatus == "inactive" {
        return "Updating status…"
    }

    preconditionFailure("Unknown status: \(newStatus)")
}

Like precondition(), calls to preconditionFailure() will remain present in your release code, so you should only use this if you’re certain the code should never be reached. However, unlike assert(), assertionFailure(), and precondition(), the preconditionFailure() function has a return type of Never, which means the Swift compiler will automatically warn you if you attempt to put executable code after it.

Another important effect of using Never is that you aren’t forced to return a nonsense value from a function when you know you’re in an invalid state. If we had used assertionFailure() in the code above, then we’d need to add an extra line returning some sort of string just to make our code compile.

If you build with unchecked optimizations, the compiler is free to assume that your assertionFailure() code is never called – it should be unreachable.

So, in our code above we’ve made it really clear that updating the user’s status to anything other than “active” and “inactive” is completely invalid – any other kind of change will damage our user’s data, so it’s better to crash rather than continue.

TL;DR: Use preconditionFailure() when continuing execution is dangerous, and it’s better to halt execution than continue. Swift will explicitly warn you if you put code after a preconditionFailure() line, because it will never return.

fatalError()

At the very top of the assertion food chain is fatalError(), which will unconditionally crash your code regardless of you building for debug or release, or even if you have unchecked optimizations enabled – it will always crash your code.

Use it like this:

guard let url = Bundle.main.url(forResource: "input", withExtension: "json") else {
    fatalError("Failed to locate input.json in bundle.")
}

Think of fatalError() as being like preconditionFailure(), except you cannot turn it off – no level of compiler adjustments will make calls to fatalError() go away, so you should use them judiciously.

Still, there are at least two reasons why fatalError() might not be for you:

  1. Sometimes a force unwrap is simpler. Jordan Rose – who worked on Swift at Apple from 2012 to 2019, said “if your else block is just going to call fatalError(), ! is more expressive. It's an assertion, not unsafe.”
  2. Using fatalError() will copy the full path to your current file into your finished program, so you might inadvertently reveal something confidential.

TL;DR: Use fatalError() if you absolutely must crash your code no matter what build settings you use. To quote Ellen Ripley, “nuke it from orbit – it’s the only way to be sure.”

Where things get fuzzy…

Three of our five options are relatively straightforward:

  • Use assert() as often as you want, because it lets you check things are how you expect without having a performance impact for customers.
  • Use assertionFailure() if there’s somewhere your code shouldn’t reach, but it’s not a disaster if it happens.
  • Use precondition() anywhere that important checks must happen in order to keep your user safe.

That leaves just fatalError() and preconditionFailure(), and there’s only one difference between them significant enough for most people: fatalError() will always cause your program to halt, whereas preconditionFailure() can be ignored depending on your build settings.

Challenges

If you’d like to take this tutorial further, here are some suggestions:

  1. Pick any project of yours, then identify and add at least 10 places where assert() can be used.
  2. Take a look at the Swift source code for assert() – how does @autoclosure allow our work to be compiled out so cleanly?

If you liked this, you'd love Hacking with Swift+…

Here's just a sample of the other tutorials, with each one coming as an article to read and as a 4K Ultra HD video.

Find out more and subscribe here


Making the most of optionals

23:07

ADVANCED SWIFT

FREE: Making the most of optionals

Swift’s optionals are implemented as simple enums, with just a little compiler magic sprinkled around as syntactic sugar. However, they do much more than people realize, and in this article I’m going to demonstrate some of their power features that can really help you write better code – and blow your mind along the way.

Using memoization to speed up slow functions

36:18

HIGH-PERFORMANCE APPS

FREE: Using memoization to speed up slow functions

In this article you’ll learn how memoization can dramatically boost the performance of slow functions, and how easy Swift makes it thanks to its generics and closures.

User-friendly network access

14:26

NETWORKING

FREE: User-friendly network access

Anyone can write Swift code to fetch network data, but much harder is knowing how to write code to do it respectfully. In this article we’ll look at building a considerate network stack, taking into account the user’s connection, preferences, and more.

Creating a WaveView to draw smooth waveforms

32:08

CUSTOM SWIFTUI COMPONENTS

FREE: Creating a WaveView to draw smooth waveforms

In this article I’m going to walk you through building a WaveView with SwiftUI, allowing us to create beautiful waveform-like effects to bring your user interface to life.

Understanding generics – part 1

20:01

INTERMEDIATE SWIFT

FREE: Understanding generics – part 1

Generics are one of the most powerful features of Swift, allowing us to write code once and reuse it in many ways. In this article we’ll explore how they work, why adding constraints actually helps us write more code, and how generics help solve one of the biggest problems in Swift.

Creating a custom property wrapper using DynamicProperty

14:20

INTERMEDIATE SWIFTUI

FREE: Creating a custom property wrapper using DynamicProperty

It’s not hard to make a basic property wrapper, but if you want one that automatically updates the body property like @State you need to do some extra work. In this article I’ll show you exactly how it’s done, as we build a property wrapper capable of reading and writing documents from our app’s container.

Making your app accessible

33:12

ULTIMATE PORTFOLIO APP

FREE: Making your app accessible

It is my firm belief that every iOS app should be usable to everyone, and putting in the work to make your app function well no matter who is using it says a lot about the kind of developer you are.

How to use phantom types in Swift

24:11

ADVANCED SWIFT

FREE: How to use phantom types in Swift

Phantom types are a powerful way to give the Swift compiler extra information about our code so that it can stop us from making mistakes. In this article I’m going to explain how they work and why you’d want them, as well as providing lots of hands-on examples you can try.

Interview questions: Introduction

3:54

INTERVIEW QUESTIONS

FREE: Interview questions: Introduction

Getting ready for a job interview is tough work, so I’ve prepared a whole bunch of common questions and answers to help give you a jump start. But before you get into them, let me explain the plan in more detail…

Challenge 1: Converter

38:12

SOLUTIONS

FREE: Challenge 1: Converter

This early challenge day asks you to build a converter app that’s able to move between any two similar units, such as kilometers and miles. Let’s solve it now, then we’ll take it further, and then we’ll take it even further

Shadows and glows

19:50

SWIFTUI SPECIAL EFFECTS

FREE: Shadows and glows

SwiftUI gives us a modifier to make simple shadows, but if you want something more advanced such as inner shadows or glows, you need to do extra work. In this article I’ll show you how to get both those effects and more in a customizable, flexible way.

Ultimate Portfolio App: Introduction

14:17

ULTIMATE PORTFOLIO APP

FREE: Ultimate Portfolio App: Introduction

While I’m sure you’re keen to get started programming immediately, please give me a few minutes to outline the goals of this course and explain why it’s different from other courses I’ve written.

Functional programming in Swift: Introduction

6:52

FUNCTIONAL PROGRAMMING

FREE: Functional programming in Swift: Introduction

Before you dive in to the first article in this course, I want to give you a brief overview of our goals, how the content is structured, as well as a rough idea of what you can expect to find.

Transforming data with map()

42:32

FUNCTIONAL PROGRAMMING

FREE: Transforming data with map()

In this article we’re going to look at the map() function, which transforms one thing into another thing. Along the way we’ll also be exploring some core concepts of functional programming, so if you read no other articles in this course at least read this one!

Trees

31:55

DATA STRUCTURES

FREE: Trees

Trees are an extraordinarily simple, extraordinarily useful data type, and in this article we’ll make a complete tree data type using Swift in just a few minutes. But rather than just stop there, we’re going to do something quite beautiful that I hope will blow your mind while teaching you something useful.

Bringing MVVM into our SwiftUI project, part 1

43:39

ULTIMATE PORTFOLIO APP

Bringing MVVM into our SwiftUI project, part 1

The final major change we’re going to make to our project is to look at how it fits in with the MVVM design pattern. I left this one to last because it’s quite a jump from our previous work and in some respects SwiftUI even fights against it, but I do think it’s worth exploring so you can be sure your code is sound.

What steps do you take to identify and resolve performance issues?

2:54

INTERVIEW QUESTIONS

What steps do you take to identify and resolve performance issues?

This is a brilliant question to ask, because it gives the interviewee the scope to pick their own area of expertise and delve into it, and it’s also a brilliant question to answer because you’re in control – you get to direct the conversation!

Last but not least: widgets

1:24:55

HACKING WITH SWIFT LIVE 2020

Last but not least: widgets

For our last topic, we’re going to explore widgets. iOS has had widget-like behavior for some time through its Today extensions, but in iOS 14 they gained a lot more functionality.

@AppStorage, ProgressView, and documents

1:01:00

HACKING WITH SWIFT LIVE 2020

@AppStorage, ProgressView, and documents

In this part we’re going to start by looking at storing app information in UserDefaults, then move on to showing progress with ProgressView, and finally loading and saving documents.

Storing data in iCloud

31:23

ULTIMATE PORTFOLIO APP

Storing data in iCloud

Now that our project is all ready for expansion, our first step will be to let users upload projects to iCloud so later on other users can view them and even comment on them. We’ll approach this in a simple way at first, but we’ll come back for improvements later.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.