My App Crashed, Now What?

In this tutorial, you’ll learn what makes your app crash and how to fix it when it does. By Ehab Amer.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

A Wider View of the Problem

Before checking the line of the crash itself, you should understand the purpose of addPressed(). The three lines do the following:

  1. Create an IndexPath object after the last row in section 0. Index 4 represents the fifth item, since indices start from 0.
  2. Tell the tableView to insert a new row at newIndex.
  3. Add the new row to the itemsList data source array.

First, look at the flow: It makes sense and it’s correct. But Xcode just told you that it’s not. So what’s wrong with it?

Narrowing Down the Problem

The exception breakpoint stopped at the second line, so the app didn’t add the new row to itemsList. At this point, it seems like a straightforward fix — add the new item to the itemsList before inserting it in the tableView. It helps to understand more about the line that caused the crash.

Make sure you’ve enabled the exception breakpoint, then build and run and open the same screen again.

Open InvalidTableUpdatesViewController.swift and add breakpoints on line 37, which caused the crash, and on line 44, which is the return of tableView(_:numberOfRowsInSection:). Press the Add button so the app stops on the first breakpoint, then press Continue. Now, look at the call stack on the left:

Call stack on the second breakpoint

Notice that insertRows(at:with:) made a call internally to tableView(_:numberOfRowsInSection:) to check the new size of itemsList. Since itemsList hadn’t updated yet, the tableView didn’t find anything added to it which put it in an inconsistent state.

In other words, you told the tableView there’s a new item, but the tableView didn’t find that itemsList grew.

This is a proof of the table view’s behavior. Move the line of code where you add the item to itemsList, between the other two lines. addPressed() should now look like this:

@IBAction func addPressed() {
  let newIndex = IndexPath(row: itemsList.count, section: 0)
  itemsList.append((itemsList.last ?? 0) + 1)
  tableView.insertRows(at: [newIndex], with: .automatic)
}

This updates the data source before updating the view. Build and run, then press the Add button to see if everything works:

Cells added without crashing

Awesome, now you’ve fixed all three screens in the app. But there’s still one more point about app crashes you should know about.

Assertions

Assertions are manually-triggered crashes you can insert into your own code. The obvious question that comes to mind is: Why would you write code to crash your own app?

That’s a very good question. However illogical it may seem, you’ll understand why this is helpful in a moment. :]

Imagine you’re writing a complicated piece of code, and there are some flows in your logic that no one should reach because reaching them means something fatally wrong has happened.

These situations are ideal for assertions. They’ll help you, or anyone else using your code, discover that something’s not working properly during development.

Writing Your Own Reusable Code

Writing a framework is also a good example where assertions can be useful. You can raise an assertion if another developer provides irrational input to your framework that won’t perform as expected.

An example of when this is handy is in ForceUnwrappingViewController.swift. Nothing will happen in showResult(result:) if result doesn’t cast to Int or String, and whoever is using your code won’t know what’s going on right away. Of course they’re doing something wrong, but wouldn’t it be awesome if the code was smart enough to tell them what?

To try it out, add this block of code at the end of showResult(result:):

else {
  assertionFailure("Only Int or Strings are accepted in this function")
}

You raise an assertion if result isn’t an Int or a String. Add this line of code at the end of calculatePressed(_:) to see how it works:

showResult(result: UIView())

Here, you send showResult(result:) a very unexpected value… a UIView!

Build and run, open the Force Unwrapping screen and press the Calculate button.

App crashed on the assertion call

Your app crashed in ForceUnwrappingViewController.swift on line 65.

As expected, the crash line is the assertion call, but you haven’t fully answered the question. Should crashing code be in the final app on the AppStore if the developer doesn’t cover all cases?

The answer to the question is: It doesn’t matter. :]

The assertions do indeed exist in your final product, but it’ll be as if they aren’t there at all.

Assertions only work while your app is building under the debug configuration. Assertions won’t do anything under the release configuration, which is how you’ll build your app when you upload it on the AppStore.

Want to see it for yourself? You’ll try it out in the next step.

Changing Your Build Configuration

Click the CrashGallery target in the upper-left corner of your Xcode window to try it out. Select Edit Scheme from the drop-down menu, then choose Run from the left-hand side of the new window and select Release from Build Configuration.

Changing your build configuration

Build and run, then press the Calculate button once more.

Sum 108 calculated and written on-screen

No crashes, no assertions. It worked normally. Your code didn’t do anything when it got an unexpected value, so this step had no effect.

But also note that the release configuration isn’t for debugging. You’ll find that when you debug with Release selected, Xcode won’t behave as expected. It might show the wrong line executing, the Variables View might not show any values or the Console Log may not evaluate expressions you print.

Use this configuration if you want to measure performance, not for code tracing and debugging.

Assertions are a handy tool to help your fellow developers or yourself fix things before you forget them. But don’t overuse them, as they can become more annoying than helpful.

Note: Use preconditionFailure(_:file:line:) or fatalError(_:file:line:) instead of assertionFailure(_:file:line:) to make your app crash under the release configuration.

Where to Go From Here?

Download the finished project for this tutorial by using the Download Materials button at the top or bottom of this page.

You’ve seen how crashes are a normal part of developing your app. You should even see them as an opportunity to learn more about the framework you’re using.

Do your best to get the most information about why a crash happened. There are multiple ways to fix each crash, and some solutions may be better than others. The more you understand the problem, the better match the solution you choose will be.

You can learn more about debugging from the video course, Beginning iOS Debugging.

I hope you enjoyed this tutorial! If you have any questions or comments, please join the forum discussion below. :]