• 20 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 8/22/18

Utilize the debugger

Log in or subscribe for free to enjoy all this course has to offer!

It's now time to solve the mystery! We have plenty of information provided in the console about the crash. Let's review it:

Tic-tac-toe console after the crash

Let's now investigate! Analyzing the stack reveals that the error occurred in the  clearBoard  function. Here's the code:

    func clearBoard() {
        for i in 0 ... board.count {
            board[i].setTitle("", for: .normal)
            board[i].backgroundColor = .white
        }
    }

May not be easy to find the error at first glance. To get some more help, we'll turn to debugging using breakpoints!

What's debugging?

Debugging is a process of identifying and removing errors from software. One of the approaches to debugging is using breakpoints.

Breakpoints

A breakpoint is an intentional stopping place in a program, put in place for debugging purposes. It allows us to pause the execution at a specific line of code and provide data about the application's state at the moment of the breakpoint.

The first point of interest is usually seeing what variables are present at the moment of a breakpoint and what values they have. 

For our first problem at hand, we’ll use an exception breakpoint.

Exception breakpoint

An exception breakpoint allows us to interrupt the code right before the code line that crashes the app.

To create this breakpoint, you have to go to the breakpoint panel. It's the second to last tab on the Utilities panel:

This tab is empty at the moment because we haven't created any breakpoints yet. To add an exception breakpoint, go to the bottom of the interface, click on the + and choose the Exception Breakpoint ... option:

Adding exception breakpoint
Adding exception breakpoint

In the popup that appears, set the Exception attribute to Objective-C:

That's it! Your exception breakpoint is created and it appears in the breakpoint browser:

In order for us to observe the power of using this type of breakpoint, all we need to do is run our app again. This time, the application will not crash, but instead, it will stop right before the line that causes the trouble! 😈

Browsing the call stack

The breakpoint takes you directly to the file and even to the problematic line:

Stopping at a breakpoint in Xcode
Stopping at a breakpoint in Xcode

Using this approach, we didn't even need to analyze the call stack, we were brought straight to the right place.

An additional benefit of stopping at a breakpoint, the execution stack is displayed on the left in the Debug Navigator allowing us to navigate within it:

Debug Navigator
Debug Navigator

To navigate different call points, simply click on the line of your choice on the left in the execution stack and you'll be immediately taken to the corresponding line of code:

Debug Navigator
Debug Navigator

Variables' lifecycle

After a bit of exploration, let's get to business and fix the issue!

We see that the problematic line is this:

board[i].setTitle("", for: .normal)

The code looks just fine. So, what's the issue?

At this point, we need to know what values troublesome variables hold.

We already have experience observing the values using  print function in the code. This function outputs a variable's value for us in the console - the right section off the bottom panel in the Xcode. Now let's take a look at the left section of it! This section shows all the live variables and exposes their values in debug mode.

Here's the appearance of the bottom panel:

Debug area
Debug panel

This view allows us to observe all active variables at the breakpoint. In our case, there are only two:

  • i : the counter of the for loop which at the moment holds a value of 9.

  • self  : this is an object instance we're in - ViewController.

We can expand those variables to see further details for structured types - like collections, which will allow us to see their individual elements. Or, classes, to view their properties.

In our example, we need to take a detailed look at the  board array, which appears to cause a problem:

Board array of buttons of ViewController
Board array of buttons of ViewController

We see the board array is of  [[UIButton]!]  type, meaning it's an array and we can see its indexed elements. But the details of each element are not that clear. 😕

Viewing variables in console

When we ran the app without an exception breakpoint, we got some information in the console as well, but now it's empty. That's because the info we observed previously was related to the crash that we didn't get to yet. 😉

Well, ok, it's not completely empty, it has  (lldb)  in it. It's a Low Level Debugger -  a command based tool. We can type commands and the debugger will provide response. 

Which commands can we type?

For now, we'll learn a single command, but one of the most useful - po, which stands for Print Object. And as the name suggests, its role is to print a given object. So, lets type it right after (lldb):

(lldb) po board

And observe the result:

result of command 'po board'
Result of 'po board' command

The console shows us details about the object and now it appears in a much more readable format.

We can see that the board array contains 9 elements of non-optional UIButton instances. There are two keys: Player.one and Player.two. Each contains 5 labels. If we go a little further, we can write:

Resolving the error

Let's now use this knowledge to see if it enables us to resolve the issue. The exception point we set up brought us to this line:

board[i].setTitle("", for: .normal)

Here are our investigation discoveries:

  • Analyzing the board array, we could see it has 9 elements with indices from 0 to 8.

  • Looking at the variable values, we could see that the value of  i  at the breakpoint is 9.

It's clear now why the code is not working - we are trying to access a non-existent element of an array.

To solve the problem, we need to ensure that the max value the  i  variable gets is 8 and not 9!

For that, we need to look at the code where we are assigning the values for the loop counter:

for i in 0 ... board.count {
    ...
}

 Well, there's no place we can see 9. We are using count property of the array instead - board.count . So, what's the matter? Shouldn't it be a calculated property? - Well, it gets a calculated property and results in 9! And, because we are using an closed range in the loop, it gets to iterate from 0 to 9!

To resolve this, all we need to do is use a half-open range instead:

for i in 0 ..< board.count {
    ...
}

This will ensure that we have iterations from 0 to 8!

Let's test it. Run the app!

It now works! The bug's been eliminated! Congrats! 💪

We've taken a long route to spot the problem. But you have to admit, it was worth learning so much along the way! The purpose of this particular mistake is to demonstrate the way of fixing it. Most real situations are very similar. It often happens that the issue is small, but the road to find it is quite long!

Debugging will soon become second nature to you and will merge into a single process of writing the code!

Let's recap!

  • A quick way to identify the source of runtime errors is to create an exception breakpoint that pauses an app execution right at the problematic line of code.

  • A breakpoint allows pausing the code execution and inspecting variables to identify a bug.

  • To inspect variables at any point of execution in debug mode, we can use the Variables view and print details in the Console of the Debug area.

Example of certificate of achievement
Example of certificate of achievement