Get Started with Debugging in Xcode

Written by Reinder de Vries on June 29 2020 in App Development, iOS

Get 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.

Here’s what we’ll get into:

  • How you can use Xcode’s debugging tools
  • Poor man’s debugging with print()
  • Breakpoints, exceptions and steppers
  • Compilation vs. runtime errors
  • A helpful workflow to find and fix bugs

Making mistakes is part of the learning process, especially if you’re learning how to build iOS apps. 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. Get Started with Debugging in Xcode
  2. Compilation vs. Runtime Errors
  3. How To Find the Root Cause of a Bug
  4. A Great Workflow for Fixing Bugs
  5. Debugging with Breakpoints in Xcode
  6. Breakpoint Steppers: Step Through Your Code
  7. Working with Exception Breakpoints in Xcode
  8. Further Reading

Get Started with Debugging in Xcode

Let’s dive in with the easiest debugging technique ever: 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 output. 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 14 and Swift 5

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

Compilation vs. Runtime Errors

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

  • Compilation 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.

Compilation errors typically show up in the Xcode editors, and usually in the Console. Runtime errors always show up in the Console, and occasionally in the Xcode editor.

In both cases, 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.

A few common bugs in Swift are:

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.

How To Find the Root Cause of a Bug

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!

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 (yikes!)

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.

Finding the error message of a bug is important, and so is being able to reproduce the bug! Focus your debugging efforts here.

A Great Workflow for Fixing Bugs

Here’s a recommended workflow for fixing bugs:

  1. Your app crashes. There’s a bug!
  2. You attempt to find the error message. Error messages are often shown in the Xcode Console, but you may have to dig a little deeper.
  3. You read and “decode” the error message. What does it mean?
  4. You come up with a solution to fix the bug and try it out. Didn’t work? 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 can Google the error message you found. This usually brings you to Stack Overflow, a blog, Reddit or Quora. In most cases Stack Overflow, or a related blog, has the answer to the error you’re seeing.

Based on what you found, you implement the solution in your own app. Even though most Stack Overflow answers explain what the cause of the bug is, you still need to do some custom coding to implement the solution.

When a ready-made bugfix can’t be found via Google, how do you debug your iOS app project? It’s also smart to not only rely on others to fix bugs for you. You can learn a ton about iOS development, just from debugging issues.

Stuck with a bug? It 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 – or a rubber ducky – you carefully step through the problem, and this usually gives you an insight in a solution. Give it a try!

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 and 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.

Quick Tip: A breakpoint will halt your code before the highlighted line is executed. It’s halted on that line. Good to know!

Let’s take a look at the tools you can use with breakpoints:

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-and-out tools more in-depth.

Breakpoint Steppers: Step 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 execute the code line by line. 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.

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…)

How does this help you solve bugs? 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.

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.

Working with 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 14 and Swift 5

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

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…

Here’s what we’ve discussed:

  • A helpful workflow to find and fix bugs
  • How you can use Xcode’s debugging tools
  • Breakpoints, exceptions and steppers
  • Compilation vs. runtime errors
  • Poor man’s debugging with print()

Want to learn more? Check out these resources:

Reinder de Vries

Hi, I'm Reinder.
I help developers play with code.

Get the Weekly

Get iOS/Swift tutorials and insights in your inbox, every Monday.
  • This field is for validation purposes and should be left unchanged.

Most Popular

Browse Topics

Swift Sandbox

Code Swift right in your browser!
Go to the Swift Sandbox

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.

×

Build great iOS apps
Learn how in my free 7-day course

  • This field is for validation purposes and should be left unchanged.

No spam, ever. Unsubscribe anytime. Privacy Policy