How To Debug iOS Apps in Xcode (II): Let’s Debug!

June 10, 2017 in

How to Debug iOS Apps in Xcode. Getting To Know The Debugger.Welcome back! This is the second episode of “How to Debug iOS Apps in Xcode”. In the first episode, we learned about the Xcode debugger, the whole debugging process and what is a breakpoint. Finally, today we are going to debug a real application, step by step.

Of course, debugging is a complex topic that can potentially fill an entire book. This tutorial will get you started, and show you how to start debugging 90% of real-case scenario iOS applications easily.L et’s dive right in!

A Broken App

First, let me introduce our sample App. It’s a math quiz application. Basically, it asks the user about the sum of two numbers. Depending on the answer of the user, it shows a success or failure message.

The App will have two different types of bugs that represent most common situations you can find in your real applications.

First, there will be a crash due to an incorrect access to an optional variable.

This covers a very common scenario where your application crashes, and you don’t know where the problem is a priori.

Then, there will also be a bug due to a miscalculation that does not actually crash the application. However, this bug will make the application behave incorrectly.

This is another very common scenario. In order to solve it, we will need to detect the problem and set a breakpoint that will allow us to debug it.

As always, you can download the code for this sample project from my Github Repository.

The solutions to these two bugs appear in the comments where the bugs are located. Nevertheless, it will be much better for you to follow this article along as I explain how to identify and fix the bugs.

However, for now, just run the app. You will be presented with a math question, enter an answer and you should see a message. Time to analyze what’s wrong with our App.

About Our App

The App contains a single UIViewController with some views including a label, a textfield and a button. The label shows the math quiz, and the button allows the user to answer the quiz after entering the answer in the textfield. Then, the result is checked against the valid value. Finally, depending on this result, a “success” or “failure” is shown to the user. The user then clicks on “Play again” to generate a new game.

There are two main methods here: generateQuiz and checkQuiz.

Generating A Game

The first one, generateQuiz, is invoked at the beginning and when the user clicks on “Play again”.

func generateQuiz() {
   // generate two random numbers
   number1 = Int(arc4random_uniform(10)) + 1
   number2 = Int(arc4random_uniform(10)) + 1

   // update UI
   answerTextField.text = ""
   quizLabel.text = "How much is \(number1!) + \(number2!)?"
   checkResponseButton.setTitle("Check response", for: .normal)

First, it generates two random numbers via the arc4random_uniform method. This method returns an Int32 from zero to the amount specified as parameter (not included). Thus, in order to generate an integer from 1 to 10, we call arc4random_uniform with 10 (generating a number from zero to nine) and then add one to the result.

Secondly, it updates the user interface elements, specially the label and the button message.

Checking The Results

Conversely, the method checkQuiz is in charge of checking the results.

func checkQuiz(_ response: String) {
   // first, parse the response string as an int
   let responseAsNumber = Int(response)!
   // then, check it.
   if responseAsNumber == number1 - number2 { // win!
      quizLabel.text = "Congratulations! You win! šŸŽ‰šŸŽ‰šŸŽ‰"
   } else { // loose!
      quizLabel.text = "Sorry, the right answer was \(number1 + number2)... šŸ˜“"
   // update UI
   checkResponseButton.setTitle("Play again!", for: .normal)

First, as the response is collected from the textfield as a string, we need to convert the result to an integer number. Then, we calculate the result and check the converted response. If they match, we have a winner! We update the label with the result and change our button to show a “Play Again” message.

How To Debug iOS Apps, Round 1. Debugging A Crash.

Imagine you get hired as the QA Engineer (Quality Assurance) for the App. One of the very first things you would do is running the application to see how it behaves. You enter a wrong number and sure enough, the application shows you the right “failure” message. Then, before trying a correct answer, you enter a text that is not a number, such as “fasd” and bum! The application crashes.

Back to your developer hat again, you receive this report of a crash, alongside a description of the steps involved to reproduce it.

The first thing you should do is add a global Exception Breakpoint like you learned in the previous episode. This will make the debugger stop when an exception occurs.

Then, you run the application with debugging active and enter a word, like “hello”. The execution stops at the checkQuiz method:

 func checkQuiz(_ response: String) {
   // first, parse the response string as an int
  let responseAsNumber = Int(response)!

A look at the debugger gives us a hint on where the problem might me:

How to Debug iOS Apps (II): Let's Debug. Digital Leaves, Learn Swift and Build Awesome iOS Apps.

Notice how we are unwrapping an optional here. This is because the result of Int(string) can be nil if… yes, exactly, if we specify a string that cannot be converted to an integer, like our “fasd” example.

Indeed, if you do a “po Int(response)”, you will get “nil” as a result. “po” is a debugger command. We will talk about them later. For now, just be aware that we forced an unwrapping on a nil optional!

Fixing Our Crash

After a face-palm, we decide to take into account this possible case by putting a guard let condition:

guard let responseAsNumber = Int(response) else {
   quizLabel.text = "Sorry, that was not even a number! šŸ˜±"
   checkResponseButton.setTitle("Play again!", for: .normal)

Thus, if we cannot convert the response string into a valid number, we show a message to the user and return, avoiding the crash. Time to test it yourself!

The Two Most Useful Debugger Commands for the Beginner

There are two commands you can use in the debugger console that are very useful when you start debugging your applications.

The debugger command console shows in the Debug Area in the right alongside the error messages. Its prompt says “(lldb)”, and you can write your commands there.

The two commands are:

  • po expression: this command evaluates the expression “expression” and shows the result. You can evaluate any variable, function call or single operation. For instance, to know the result of “Int(response)” before, we did a “po Int(response)”. Also, you could have done a “po response” to get the value of the response string, or even a “po checkQuiz(“123456″)”.
  • bt: this command dumps a backtrace of the stack at that current point. This allows us to get the list of calls that led us here, and can be helpful to trace the problem.

Learning To Identify The Cause Of A Crash

Even though it’s not always that easy to decipher Xcode error messages, most of they times they give us a clear indication of what’s happening.

However, the error is not always directly isolated to one instruction, like in our example.

Most of the times, however, we will need to navigate back to the circumstances that take us to the line that’s crashing.

An access to a nil variable can have its origin way back in the execution stack. You need to trace your code back to that instruction.

In these situations, the Debug Navigator is a very useful tool. It shows you the execution stack, with the sequence of function calls that led to the application crashing.

Furthermore, going back in debugging mode to that call sequence will show you the state of the variables of every function involved. This is essential to locate where things went wrong.

Don’t worry if this might seem overwhelming at first. As a matter of fact, is just a question of patience and practice. The more you debug, the better you will become at spotting and fixing bugs.

How To Debug iOS Apps, Round 2. Debugging a Bug.

Next, still excited to have fixed our very first crash, we keep on using the application and, when asked “How much is 3 + 6?”, we enter 9 and… get a failure message? The message says something like: “Sorry, the right answer was 9”.

Obviously, this is an undesired behavior. The app is not crashing this time, but of course you need to fix this before publishing the App.

While there are many places where the bug may be producing, our knowledge of the source code can give us a clue to help us find the error.

Concretely, the checkQuiz method, where the result is checked, is the perfect candidate to start our search. Thus, let’s add a breakpoint at the very first instruction of that method:

How to Debug iOS Apps (II): Let's Debug. Digital Leaves, Learn Swift and Build Awesome iOS Apps.Next, making sure the breakpoints are enabled (in blue), run the application. Enter a correct value in the textfield and click “Check Result”. The execution will stop at our breakpoint.

Error Spotting Techniques, One Instruction At A Time

In the debugging area on bottom, at the left panel, you will notice that our parameter “response” has the value that you entered in the textfield. In contrast, the “responseAsNumber” variable will appear uninitialized. Indeed, when the execution reaches, the instruction where the debugger stops has not been executed yet. Thus, our responseAsNumber variable has not yet been assigned.

Then, click on the “Step Over” button (fourth button from the left in the debugger area bar). The execution will move past that instruction. You will notice that, in effect, the instruction has been processed, and the responseAsNumber variable is not initialized and hopefully contains a valid integer. You should be in this instruction now:

if responseAsNumber == number1 - number2 { // win!

First Technique: review the instruction’s logic.

Probably, just by looking at that instruction, you can already spot the error. We used a subtraction instead of a sum to calculate the correct answer. This obviously causes the application to show a “failure” message even when the answer is correct. In fact, it will also cause the application to show a success message in some incorrect responses.

Second Technique: check the variables involved.

However, if you didn’t spot the bug, you could have used debugger commands to check the parameters, the two numbers and the responseAsNumber variable:

(lldb) po number1
ā–æ Optional<Int>
 - some : 5
(lldb) po number2
ā–æ Optional<Int>
 - some : 5
(lldb) po responseAsNumber

These are valid values, and indeed, number1 + number2 equals responseAsNumber, so the only possible mistake here is the calculation.

Third Technique: observe the instruction flow and the evaluation of conditional operations.

Another important clue is the result of the conditional check in the “if”. If you click on “Step Over” again, you would be expected to be taken to the immediate next instruction:

quizLabel.text = "Congratulations! You win! šŸŽ‰šŸŽ‰šŸŽ‰"

However, you land on the “failure” instruction in the “if” logic:

quizLabel.text = "Sorry, the right answer was \(number1 + number2)... šŸ˜“"

Thus, you get a clear indication that there’s something wrong in the “if” condition.

Fixing Our Bug

In order to fix our crash, we just need to change the operation that checks the result of the sum:

if responseAsNumber == number1 + number2 { // win!

Finally, run the application again, enter a valid value, and Voila! A success message appears on screen. Congrats!

Final Round! You Won!

In summary, in this “How To Debug iOS Apps” tutorial series, you learned to Debug iOS Apps in Xcode. While the previous episode focused on showing you the basics of debugging and what is a breakpoint, this episode was a practical workshop, aimed at teaching you to debug iOS Apps with a real application.

Of course, this is only the tip of the iceberg. Debugging applications is a complex topic that deserves a whole book on its own. Nevertheless, this basic tutorial will get you started and help you start getting practice debugging your applications.