UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

< 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


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.

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…

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.

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.

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.

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.

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.

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.

Ultimate Portfolio App: Introduction

11:03

ULTIMATE PORTFOLIO APP

FREE: Ultimate Portfolio App: Introduction

UPDATED: 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.

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!

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.

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.

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.

iPadOS sidebars and labels

59:06

EVENTS

iPadOS sidebars and labels

We’re going to implement your sidebar knowledge in Journeys so you can see it in action, then move on to examine the new Label view in iOS 14.

Cleaning up CloudKit, part 1

26:33

ULTIMATE PORTFOLIO APP

Cleaning up CloudKit, part 1

We’ve added quite a bit of iCloud functionality in the last few tutorials, so before we’re done we need to clean up what we have so it’s a solid foundation we can build on. To start with, that means tackling removing data as well as we handling adding data.

SwiftData

23:25

SOLUTIONS

SwiftData

This challenge asks you to upgrade iExpense to use SwiftData, adding sorting and filtering at the same time. Let’s tackle it now…

What is the difference between weak and unowned?

1:36

INTERVIEW QUESTIONS

What is the difference between weak and unowned?

Broken record alert: explain the fundamental technical difference between the two, try to provide an opinion on which you think works better, then provide some kinds of examples of where each might work well.

Creating a day/night cycle

53:37

REMAKING APPS

Creating a day/night cycle

One of the most beautiful parts of the Weather app is the way it smoothly transitions between day and night – it doesn’t just go from black to blue, but instead mimics both sunrise and sunset, smoothly animating between the two. In this tutorial we’re going to recreate that same effect in our own app.

Creating a StretchingHeader that sticks to the top of a view

18:35

CUSTOM SWIFTUI COMPONENTS

Creating a StretchingHeader that sticks to the top of a view

When users scroll beyond the top of a scroll view the default behavior is to show some empty space, but many apps prefer to show a stretchy header area instead. In this article I’ll show you how to build that SwiftUI, making an image that stays fixed to the top no matter what.

What open source projects have you contributed to?

1:54

INTERVIEW QUESTIONS

What open source projects have you contributed to?

This question comes up a lot, but I’m always wary of it because it places a lot of emphasis on unpaid work out of hours, which particularly affects people who already have extra responsibilities caring for children or elders.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.