The Xcode Debugger: LLDB

To continue your debugging experiments, you are going to add another bug to your application. Add the code below to ViewController.swift. Notice that you will be using an NSMutableArray, the Objective-C counterpart of Swift’s Array, to make the bug a little harder to find.

@IBAction func buttonTapped(_ sender: UIButton) {
    print("Method: (#function) in file: (#file) line: (#line) called.")

    badMethod()
}

func badMethod() {
    let array = NSMutableArray()

    for i in 0..<10 {
        array.insert(i, at: i)
    }

    // Go one step too far emptying the array (notice the range change):
    for _ in 0...10 {
        array.remove(at: 0)
    }
}

Build and run the application to confirm that a tap on the button results in the application crashing with an uncaught NSRangeException exception. Use your freshly acquired knowledge to study and interpret the error message as much as possible.

If you used a Swift Array type to create this bug, Xcode would have been able to highlight the line of code that caused the exception. Because you used an NSMutableArray, the code that raised the exception is deep within the Cocoa Touch framework. Frequently this is the case when debugging; problems are not so obvious and you need to do some investigative work.

Setting breakpoints

Assume that you do not know the direct cause of the crash. You just know it happens after you tap the application’s button. A reasonable way to proceed would be to stop the application after the button is tapped and step through the code until you get a clue as to the problem.

Open ViewController.swift. To stop an application at a specified location in the code, you set a breakpoint. The simplest way to set a breakpoint is to click on the gutter to the left of the editor pane next to the line where you want execution to stop. Try it: Click to the left of the line @IBAction func buttonTapped(_ sender: UIButton) {. A blue marker indicating the new breakpoint will appear (Figure 9.3).

Figure 9.3  Setting a breakpoint

Screenshot of the editor area depicts the disabling a breakpoint, in which the highlight of the code line has turned paler shade and it is selected.

After a breakpoint is set, you can toggle it by clicking on the blue marker directly. If you click on the marker once, it will become disabled, indicated by a paler shade of blue (Figure 9.4).

Figure 9.4  Disabling a breakpoint

Screenshot of the editor area depicts the disabling a breakpoint, in which the highlight of the code line has turned paler shade.

Another click re-enables the breakpoint. You can also enable, disable, delete, or edit a breakpoint by Control-clicking on the marker. A contextual menu will appear, as shown in Figure 9.5.

Figure 9.5  Modifying a breakpoint

Screenshot of the Modify breakpoint menu.

Selecting Reveal in Breakpoint Navigator opens the breakpoint navigator in Xcode’s left pane with a list of all the breakpoints in your application (Figure 9.6). You can also open the breakpoint navigator by clicking its icon in the navigator selector.

Figure 9.6  The breakpoint navigator

Screenshot of the Breakpoint navigator menu.

Stepping through code

Make sure your breakpoint on the buttonTapped(_:) method is set and active after all the clicking you did in the previous section. Run the application and tap on the button.

Your application hits the breakpoint and stops executing, and Xcode takes you to the line of code that would be executed next, which is highlighted in green. It also opens some new information areas (Figure 9.7).

Figure 9.7  Xcode stopped at a breakpoint

Screenshot depicts the output of a Xcode breakpoint, with its various panes labeled.

You are familiar with the console and have already seen the debug navigator. The new areas here are the variables view and the debug bar, which together with the console make up the debug area. (If you cannot see the variables view, click on the Show variables view icon is displayed. icon on the bottom-right corner of the debug area.)

The variables view can help you discover the values of variables and constants within the scope of the breakpoint. However, trying to find a particular value can require a fair amount of digging.

Initially, all you will see listed in the variables view are the sender and self arguments passed to the buttonTapped(_:) method. Click on the disclosure triangle for sender, and you will see that it contains a UIKit.UIControl property. Within it there is a _targetActions array that contains the button’s attached target-action pairs.

Open the _targetActions array, open the first item ([0]), and then select the _target property. Tap the space bar while _target is selected, and a Quick Look window will open, showing a preview of the variable (which is an instance of ViewController). The Quick Look is shown in Figure 9.8.

Figure 9.8  Inspecting variables in the variables view

Screenshot shows inspecting variables in the variables view using a Quick Look window.

In the same section as the _target, you will see the _selector. Next to it, you will see (SEL) "buttonTapped:". The (SEL) indicates that this is a selector, and "buttonTapped:" is the name of the selector.

In this contrived example, it does not help you much to dig to find the _target and the _action; however, once you start working with larger, more complex applications, it can be especially useful to use the variables view. You do need to know what you are looking for, such as the _target and the _action – but finding the value that you are interested in can be very helpful in tracking down bugs.

Now it is time to start advancing through the code. You can do this using the buttons on the debug bar, shown in Figure 9.9.

Figure 9.9  The debug bar

Screenshot of the Debug bar with its tool icons labeled.

The important buttons in the debug bar are:

  • Continue program execution (Continue Execution icon is shown.) – resumes normal execution of the program

  • Step over (A step over icon is shown.) – executes a single line of code without entering any function or method call

  • Step into (A step into icon is shown.) – executes the next line of code, including entering a function or method call

  • Step out (A step out icon is shown.) – continues execution until the current function or method is exited

Click the The debug bar button until you highlight the badMethod() line (do not execute this line). Note that you do not step into the print() method – because it is an Apple-written method, you know there will be no problems there.

With badMethod() highlighted, click the The debug bar button to step into the badMethod() method, and continue stepping through the code with The debug bar until the application crashes. It will take you quite a few clicks, and it will look like you are going through the same lines of code over and over – in fact, you are, as the code loops over the ranges.

As you step through the code, you can pause to mouseover i and array.remove to see their values update (Figure 9.10).

Figure 9.10  Examining the value of a variable

Screenshot of a portion of the Editor area with a pop-up list under the variable “array.”

Once the application crashes, you have confirmation that the crash occurs within the badMethod() method. With this knowledge you can now delete or disable the breakpoint at the func buttonTapped(_ sender: UIButton) line.

To delete a breakpoint, Control-click it and select Delete Breakpoint. You can also delete a breakpoint by dragging the blue marker out of the gutter, as shown in Figure 9.11.

Figure 9.11  Dragging a marker to delete the breakpoint

Screenshot shows the action of dragging a marker to delete a breakpoint. A cursor accompanied by a cross mark symbol is shown to be placed outside the gutter, while the code line is highlighted.

Occasionally, you want to be notified when a line of code is triggered, but you do not need any additional information or for the application to pause when it hits that line. To accomplish this, you can add a sound to a breakpoint and have it automatically continue execution after being triggered.

Add a new breakpoint at the array.insert(i, at: i) line of the badMethod() method. Then Control-click on the marker and select Edit Breakpoint.... Click on the Add Action button and select Sound from the pop-up menu. Finally, check the box to Automatically continue after evaluating actions (Figure 9.12).

Figure 9.12  Enabling special actions

Screenshot depicts Enabling special actions after selecting Edit Breakpoint.

You have configured the breakpoint to make an alert sound instead of stopping execution every time it is encountered. Run the application again and tap the button. You should hear a sequence of sounds, and then the application will crash.

It seems the application is safely completing the for loop, but you need to be sure. Find and Control-click your breakpoint marker again, selecting Edit Breakpoint... as before. In the editor pop-up, click the + to the right of the sound action to add a new action.

From the pop-up, select Log Message. In the Text field, enter Pass number %H (%H is the breakpoint hit count, a reference to the number of times the breakpoint has been encountered). Finally, make sure the Log message to console radio button is selected (Figure 9.13).

Figure 9.13  Assigning multiple actions to a breakpoint

Screenshot shows adding a breakpoint with special action “Log message, log message to console” added to the line array. remove.

Run the application again and tap the button. You will hear the sequence of sounds again, and the application will crash as before. But this time, if you watch the console (or scroll up after the application crashes), you will see that the breakpoint was encountered 10 times. This confirms that your code is completing the loop safely.

Delete your current breakpoint and add a new one on the line array.remove(at: 0). Edit the breakpoint to log the pass number and continue automatically, as before (Figure 9.14).

Figure 9.14  Adding a logging breakpoint

Screenshot shows how to add multiple actions to a breakpoint.

Run the application and tap the button. When it crashes, scroll up in the console and you will see that the second breakpoint was encountered 11 times. That is one time too many, and you have your smoking gun. It also explains the NSRangeException logged on the console as the application crashes. Carefully read the crash log on the console again and make as much sense of it as possible.

Before fixing the problem, take the time to explore a couple more debugging strategies. First, disable or delete any remaining breakpoints in the application.

In these simple examples, you have known just where to look to find the bug in your code, but in real-world development you will often have no idea where in your application a bug is hiding. It would be nice if you could tell which line of code is causing an uncaught exception resulting in a crash.

It would be nice – and with an exception breakpoint, you can do just that. Open the breakpoint navigator and click on the + in the lower-left corner of the window. From the contextual menu, select Exception Breakpoint.... A new exception breakpoint is created and a pop-up appears. Make sure it catches all exceptions on throw, as shown in Figure 9.15.

Figure 9.15  Adding an exception breakpoint

Screenshot shows adding an exception breakpoint.

Run the application and tap the button once again. The application automatically stops and Xcode takes you to the line that directly causes the exception to be raised. Note, however, that there is no console log. That is because the application has not crashed yet. To see the crash and read the cause, click on the Adding an exception breakpoint button on the debug bar until you see the crash.

This strategy is the one to begin with as you tackle a new bug. In fact, many programmers always keep an exception breakpoint active while developing. Why did we make you wait so long to use it? Because if you had started with an exception breakpoint, you would not have needed to learn about the other debugging strategies, and they have their uses, too. Feel free to remove this breakpoint if you would like; you will not need it again.

You are going to try one final technique: the symbolic breakpoint. These are breakpoints specified not by line number, but by the name of a function or method, referred to as a symbol. Symbolic breakpoints are triggered when the symbol is called – whether the symbol is in your code or in a framework for which you have no code.

Add a new symbolic breakpoint in the breakpoint navigator by clicking the + button on the lower-left corner and, from the contextual menu, selecting Symbolic Breakpoint.... In the pop-up, specify badMethod as the symbol, as shown in Figure 9.16. This means that every time badMethod() is called, the application will stop.

Figure 9.16  Adding a symbolic breakpoint

Screenshot depicts adding a symbolic breakpoint.

Run the application to test the breakpoint. The application should stop at badMethod() after you tap the Tap me! button.

In a real-world app, it is rare that you would use a symbolic breakpoint on a method that you created; you would likely add a normal breakpoint like the ones you saw earlier in this chapter. Symbolic breakpoints are most useful to stop on a method that you did not write, such as a method in one of Apple’s frameworks. For example, you might want to know whenever the method loadView() is triggered for any view controller within the application.

Finally, fix the bug.

func badMethod() {
    let array = NSMutableArray()

    for i in 0..<10 {
        array.insert(i, at: i)
    }

    // Go one step too far emptying the array (notice the range change):
    for _ in 0...10 {
    for _ in 0..<10 {
        array.remove(at: 0)
    }
}

The LLDB console

A great feature of Xcode’s LLDB debugger is that it has a command-line interface. The console area is not only used to read messages, but also can be used to type LLDB commands. The debugger command-line interface is active whenever you see the blue (lldb) prompt on the console.

Make sure your symbolic breakpoint on badMethod() is still active, run the application, and tap the button to break at that point. Look at the console and you will see the (lldb) prompt (Figure 9.17). Click beside the prompt, and you can type commands.

Figure 9.17  The (lldb) prompt on the console

Screenshot displays the console after LLDB is enabled. The interface has a message followed by LLDB enclosed in parentheses, the entire text is highlighted.

One of the most useful LLDB commands is print-object, abbreviated po. This command prints a nice description of any instance. Try it out by typing on the console.

(lldb) po self
<Buggy.ViewController: 0x7fae9852bf20>

The response to the command is that self is an instance of ViewController. Now advance one line of code with the command step; this will initialize the array constant reference. Print the reference’s value with po.

(lldb) step
(lldb) po array
0 elements

The response 0 elements is not very useful, as it does not give you a lot of information. The print command, abbreviated p, can be more verbose. Try it.

(lldb) p array
(NSMutableArray) $R3 = 0x00007fae98517c00 "0 values" {}

Frequently, using the console with print or print-object to examine variables is much more convenient than Xcode’s variables view pane.

Another useful LLDB command is expression, abbreviated expr. This command allows you to enter Swift code to modify variables. For example, add some data to the array, look at the contents, and continue execution.

(lldb) expr array.insert(1, at: 0)
(lldb) p array
(NSMutableArray) $R5 = 0x00007fae98517c00 "1 value" {
  [0] = 0xb000000000000013 Int64(1)
}
(lldb) po array
▿ 1 element
  - [0] : 1
  (lldb) continue

Perhaps more surprisingly, you can also change the UI with LLDB expressions. Try changing the button’s tintColor to red.

(lldb) expr self.view.tintColor = UIColor.red
(lldb) continue

There are many LLDB commands. To learn more, enter the help command at the (lldb) prompt.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset