Getting Started With Debugging In Xcode

Written by Reinder de Vries on July 19 2019 in App Development

Getting Started With Debugging In Xcode

Finding and fixing bugs in your app is exciting, isn’t it? … No? You probably don’t enjoy debugging in Xcode, but it doesn’t need to be a drag. In this article, you’ll learn a few expert techniques that make debugging your iOS apps a breeze.

Making mistakes is part of the learning process, especially if you’re a relative beginner at iOS development. Finding and fixing bugs can be frustrating if you don’t know where to look. The debugging tools you find in Xcode will help you find the root cause of a bug quickly – and save you from a lot of frustration.

Ready? Let’s go.

  1. Getting Started With Debugging In Xcode
  2. Compile-Time And Runtime Errors
  3. Finding A Bug’s Root Cause
  4. A Great Workflow For Fixing Bugs
  5. Debugging With Breakpoints In Xcode
  6. Stepping Through Your Code
  7. Using Exception Breakpoints In Xcode
  8. Further Reading

Getting Started With Debugging In Xcode

Let’s dive in, with what’s called poor man’s debugging. You can do this with the print() function, like this:

let tweet = Tweet()
tweet.save()

print("Saving tweet with ID = \(tweet.id)")

In the above code, we’re saving a hypothetical Tweet object. On the last line, we’re printing a bit of text to the Console. When the line of code is executed, it’ll print the text. Printing things like this can help you figure out what’s going on in your code.

Debugging with print() is so simple, but so powerful. Some iOS developers will say you should use actual debugging tools, like breakpoints, but they don’t know how helpful code peppered with some print()‘s can be for your learning experience. You can see what’s going on, with print().

Quick Tip: You can use literal expressions to print function names, line numbers and filenames with #function, #line and #file. That way you know exactly where print output comes from. Like this: print("\(#file):\(#line), \(#function) --- FUSRODAH!") or print(#function). Neat!

Xcode Debugging with print()

Learn how to build iOS apps

Get started with iOS 13 and Swift 5

Sign up for my iOS development course, and learn how to build great iOS 13 apps with Swift 5 and Xcode 11.

Compile-Time And Runtime Errors

Before we continue, let’s discuss two types of errors (and bugs).

  • Compile-Time Errors: These errors and bugs show up when your app is compiled (or “built”). For example, you’ve made a typo and the Swift compiler chokes on it.
  • Runtime Errors: These errors and bugs happen when your app is running on an iPhone. The way your app works leads to an error or exception, and your app crashes.

Both types of errors typically show up in the Console. You’ll see a message that describes the error. When your app runs on an actual iPhone, and you’ve connected the iPhone to your Mac, you’ll be able to see the error messages the app spits out. And in many cases, Xcode will even point you to the line the error happened on!

Runtime error in Xcode

It’s important to note here that an error message may not directly point to the root cause of an error. It could happen, for example, that your app crashes due to an error somewhere in the code. The actual cause of the crash is at some other place in your code.

In the above screenshot, for example, the error message is Index out of range. The highlighted line isn’t to blame here, but the code a few lines above it. We’ve accidentally reported the wrong size of the places array – and that’s causing bugs further down.

Finding A Bug’s Root Cause

Imagine you’ve just coded a feature in your app, and when you’re about to finish implementing the feature – your app crashes. Oops! There’s a bug somewhere! Now what?

Fixing bugs, as frustrating as it may sometimes be, can be very exciting. It’s like being a detective, but with code. You get to trace the bug back to its roots, and then solve it. When you look at it this way, finding and squashing bugs can be pretty fulfilling!

And think about how much you can learn about iOS development while solving a bug. You can become a 10x better coder by just solving bugs…

In general, there are 3 types of bugs:

  1. Bugs that have a clear error message, like “Index out of bounds”
  2. Bugs that don’t have a clear error message, like EXC_BAD_ACCESS
  3. Bugs that don’t have an error message at all

When you’re debugging in Xcode, you’ll always want to work toward finding an error message. You can’t fix a bug if you don’t have a clear error message. If possible, you also want to be able to reproduce the bug when needed. That way you can test if you actually solved the bug, later on.

A Great Workflow For Fixing Bugs

Here’s a recommended workflow for fixing bugs:

  1. Your app crashes. There’s a bug! (It also happens that you simply notice yourself that your app doesn’t function as intended.)
  2. You attempt to find the error message. Error messages are often shown in the Xcode debugger, but sometimes you have to dig a little deeper.
  3. You then interpret the error message – what does it mean?
  4. You come up with a solution to solve the bug, and then try it out. If this doesn’t solve it, you go back to step 2.

This makes bug fixing sound really simple, right? Obviously, a lot of work can go between step 3 and 4 – the bug fixing process is simple, but not easy. (There’s a difference!)

In many cases, especially if you’re a beginner coder, you immediately Google the error message you found. This usually brings you to StackOverflow, a blog, Quora or a similar search results. In most cases StackOverflow, or a related blog, has the answer to the error you’re seeing.

Based on what you found with Google, you implement the solution in your own app. Even though most StackOverflow answers can explain well what the cause of the bug is, and it’s solution, implementing the solution in your own app still requires you to do customized coding work.

However, when a ready-made solution can’t be found on the internet – how do you debug your Swift code? (It’s also smart to not only rely on others to fix bugs for you.)

It often helps to explain the problem to someone else – I call this “Solve by House”, named after the problem-solving technique the fictional Dr. House uses in the TV series House M.D. Programmers often call this rubber duck debugging. By explaining the problem to someone else, you carefully step through the problem, and this usually gives you an insight in a solution. Try it!

Debugging With Breakpoints In Xcode

A great approach to finding and fixing bugs in Xcode is by using breakpoints. With a breakpoint, the Xcode debugger can stop your code at almost any moment and show you the exact state of your app at that point. You can do that by adding a breakpoint to a line of code in your app.

When the app executes, and then “hits” that line of code, execution of your code comes to a halt, and you can look inside the guts of your app. You can inspect the exact values of variables, properties etc. at that point. And from there, you can step line-by-line through your code.

Xcode Debugger Breakpoints

In the above image, you can see:

  • The stacktrace. You can think of the stacktrace as a “function history” or “breadcrumb”. With it you can see the functions that were called before the breakpoint was reached. For instance, in the screenshot you can see the current function, viewDidLoad().
  • The breakpoint. You can set a breakpoint by clicking on the gutter, right before a line. A blue arrow should appear. You can deactivate the breakpoint by clicking it again, and remove it by right-clicking.
  • The breakpoint actions. You can take several actions when a breakpoint is hit, like continuing execution. More on these actions, later.
  • The app values / state. You can inspect the values at the current point in the app’s execution. In the example, you can clearly see the contents of the names array.

When you look at the bug in the previous example, where you tried to access item with index no. 8 in an array with only 5 items, you see that using a breakpoint you can inspect the size of names. This has the same effect as print – although breakpoints are more advanced.

Let’s take a look at the debugger tools:

Breakpoint Actions Debugger

From left to right, you’ve got several options:

  1. Hide the debug area. Pretty self-explanatory!
  2. Deactivate breakpoints. This button deactivates all breakpoints for the current session.
  3. Continue program execution. This will continue execution of your code, until the debugger hits the next breakpoint. Keep in mind that this can be the same breakpoint, for instance when your code repeats or reaches the same point again.
  4. Step over. This continues the code, but only to the next line.
  5. Step into. This continues the code, by stepping into the current line. You go one level deeper, for instance by going into another function.
  6. Step out. This continues the code, by stepping out of the current function. You go one level higher.

Let’s discuss those step over-into-out tools.

Stepping Through Your Code

With a stepper, you can step through the lines of code of your app. You can literally start at one line, then continue to the next, and the next, and so on. You can also step into (and out of) function calls, to follow the “red thread” into a function.

Think of your code as a tree-like structure. You start at one point, then go into a function, and when the function finishes, you go out of the function again. It’s like how a caterpillar would eat through a cake, with different levels.

When you’re going over your code line-by-line with breakpoints, you can decide to “step over” a function, or go “into” that function. When you go in, you can go out again too.

Quick Tip: When a line with a breakpoint is reached, that line is not yet executed. The breakpoint sits “before” that line. You can clearly see that in the above screenshots. The app crashes on the line that has the breakpoint, but you can see in the debug output that the app hasn’t crashed yet (no error message). So, when you step over line 21, i.e. execute that line, the app crashes.

Practicing stepping through lines of code is easiest when you code a simple loop. You can see in the image below that there’s a for-loop that calculates the sum of an array of numbers. The code breaks on the line inside the for-loop.

Make sure that you understand this, about the example above:

  • The loop has ran for 5 times, from n = 0 to 4
  • At this point, n = 5 but numbers[5] hasn’t been added to sum yet
  • The variable sum is equal to 1 + 3 + 7 + 9 + 14, the sum of numbers[n] from 0 to 4

Stepping into a function isn’t always possible, or helpful, because you can only go over your own code step-by-step. You can’t inspect the source code of UIKit frameworks, so when you try to go in, you’ll only see assembly code. (You can debug this, but that’s for another blog post…)

So… how does this help you solve bugs? Well, you can inspect the values your code has. In many cases, bugs occur because your app gets to a state that you hadn’t expected. Inspecting your code often gives you insight into the state of your app, and helps you to solve bugs.

Using Exception Breakpoints In Xcode

In some cases you can’t set a breakpoint in your code, because a bug occurs in a framework or Cocoa Touch library that you don’t control. When you can’t reach those lines of code, you can’t set a breakpoint.

How do you debug that? That’s where exceptions come in. You might know them from Swift error handling with do-try-catch. Some functions can “throw” exceptions when an error occurs.

You’re supposed to catch those errors with a do-catch block, but in many cases you deliberately want to crash the app so you can solve the bug that causes the exception.

A fairly typical exception is the NSInternalInconsistencyException. This is a general error exception that’s thrown in a scenario where your code gets to a state it can’t support.

When such an exception is raised, Xcode won’t always take you to the line that caused the exception. You can still find it, like this:

Exception Breakpoint

Instead of setting a breakpoint on a specific line, you set a breakpoint for any exception that’s thrown in your app code. When an exception is thrown, Xcode will take you to the line of code that caused it. This often helps you to pinpoint the line of code that causes the error.

You can set an exception breakpoint by going to the Breakpoint navigator, on the left of Xcode, and then clicking on the small +-button at the bottom-left. Then, choose Exception Breakpoint. You can also add several other breakpoints, for instance for the Swift Error type. That’s it!

Learn how to build iOS apps

Get started with iOS 13 and Swift 5

Sign up for my iOS development course, and learn how to build great iOS 13 apps with Swift 5 and Xcode 11.

Further Reading

Solving bugs is exciting! You get a chance to dive into your app’s code, figure out why it isn’t working, while learning about development. Debugging can also be frustrating…

Want to learn more? Check out these resources:

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.