Chapter 1. Using the Xcode Debugger

In This Chapter

  • Understanding the kinds of errors that may come up

  • Using Xcode's Debugger

  • Zeroing in on errors the Debugger can help you find

  • Stamping out logic errors with the Debugger

  • Using the Static Analyzer to analyze your code for memory leaks

When you're developing an application, sometimes things don't work out quite the way you planned — especially when you knock over a can of Jolt Cola on the keyboard and fry it out of existence.

Murphy was an optimist about computer programming with his law that there is always one more bug. It took Weinberg's Second Law to put debugging into perspective: If builders built buildings the way that programmers program programs, the first woodpecker to come along would destroy civilization.

As this experienced co-author learned the hard way (indeed, Tony wrote Murphy's Computer Laws in 1980 for Celestial Arts, only to violate most of them in his latest iPhone app project), debugging is not something to put off until later, after the warnings and error messages pile up. Keep in mind Bove's Theorem: The remaining work required to finish a project increases as the deadline approaches. It's best to tackle any errors and warnings you get immediately.

So, what does it take to tackle the inevitable errors that will find their way into your code? In a word, debugging: the process of analyzing your code line by line to view your program's state at a particular stage of execution. To debug a program, you run it under the control of a debugger, which lets you pause the program and examine its state. In this chapter, I show you how to use the Xcode Debugger to understand, locate, and fix bugs.

Understanding Bugs

"Stuff happens," in the immortal words of a famous ex-U.S. Secretary of Defense. When it comes to developing your own programs, that "stuff" comes in three categories:

  • Syntax errors: Compilers — the Objective-C compiler in Xcode is a case in point — expect you to use a certain set of instructions in your code; those instructions make up the language it understands. When you type If instead of if, or the subtler [view release} (with a curly closing bracket) instead of [view release] (with a straight closing bracket), the compiler suddenly has no idea what you're talking about and generates a syntax error.

    Syntax errors are the most obvious, simply because your program won't compile (and therefore won't run) until all of them are fixed. Generally, syntax errors spring from typographical errors. (And yes, the errors can be pretty penny-ante stuff — an I for an i, for goodness sake — but it doesn't take much to stump a compiler.)

    In Figure 1-1, you can see an example of a syntax error — simply forgetting to put a semicolon at the end of the CGRect frame2 = scrollView.frame statement. This one is kindly pointed out by Xcode's friendly Debugger feature. After choosing Build

    Understanding Bugs

    A syntax error in a different place in the code might not be detected by the compiler and might therefore wreak havoc with subsequent code, causing the build to fail. For example, in Figure 1-2, I forget to include a semicolon at the end of the @synthesize speed statement, and as a result, the compiler found a problem with subsequent code. Click the exclamation mark, and Xcode brings up the Build Results window, as shown in the upper part of Figure 1-3, with a long list of errors starting with Expected ';' before '-' token. The Build Results window shows a more detailed view of the consequences of an error.

    Tip

    It's generally better to ignore the subsequent errors after the first syntax error because they may be the result of that first error. In this case, because of the first error, that line and the next one were treated as a single instruction, causing an incomplete implementation of MainViewController.

    A syntax error. Oops.

    Figure 1-1. A syntax error. Oops.

    A subtle but more damaging syntax error.

    Figure 1-2. A subtle but more damaging syntax error.

  • Runtime errors: Runtime errors cause your program to stop executing — it crashes, in other words, as in "crash and burn to much wailing and gnashing of teeth." Something might have come up in the data that you hadn't expected (a division-by-zero error, for example), or the result of a method dealt a nasty surprise to your logic, or you sent a message to an object that doesn't have that message implemented. Sometimes you even get some build warnings for these errors; often the application simply stops working or hangs (stops and does nothing), or shuts down.

    The Build Results window lists the damage from this one error.

    Figure 1-3. The Build Results window lists the damage from this one error.

  • Logic errors: Your literal-minded application does exactly what you tell it to, but sometimes you unintentionally tell it the wrong thing, and it coughs up a logic error. For example, in Figure 1-4, I deliberately created a logic error by dividing by zero. Xcode warns you about the divide-by-zero error but goes ahead anyway and builds and runs the app.

    Oh, great — it builds but doesn't work.

    Figure 1-4. Oh, great — it builds but doesn't work.

I typed the divide-by-zero error (speed = kMaxSpeed/0) to make the point that you may be able to build and run your app, but it may not work as intended. In this case, the app starts up but doesn't display the animated default words.

You can see in Figure 1-4 the yellow exclamation point, which is a warning (rather than a red one, which is an error), and the message Division by zero. Clicking the exclamation point brings up the Build Results window, as shown in Figure 1-5, with the Division by zero warning and the steps of compiling and building the app.

The Build Results window shows what happened.

Figure 1-5. The Build Results window shows what happened.

Tip

With a complex app, you might be pelted with compiler warnings that you don't have time to take care of because they have no impact on the execution of the program. One reason to set Xcode preferences to always open the Build Results window is to continually remind yourself about these warnings if you haven't fixed them. To set this preference, choose Xcode

The Build Results window shows what happened.

Syntax errors, runtime errors, and logic errors can all be pains in the behind, but there's no need to think of them as insurmountable roadblocks. You're still on your way to a cool iPhone app.

Using the Debugger

The Debugger can be really useful when your program isn't doing what you expect. For the blatant errors, the Debugger can show you exactly what is going on when the error occurred. It provides you with a trail of how you got to where you are, highlights the problem instruction, and shows you your application's variables and their values at that point.

Tip

If you've been following the examples in Book III for developing the DeepThoughts app, you're ready to debug the app, and your configuration should still be set to Simulator-3.1 | Debug in the pop-up menu in the upper left corner of the Xcode Project window. If you've been developing a project with a different configuration, you must change it to the Debug build configuration. Before you can take advantage of the Debugger, the compiler must collect information for the Debugger, and the Debug build configuration generates the debugging symbols for that purpose.

You can tap the Debugger from the Xcode Text editor and set breakpoints that stop execution at any point and trace the messages sent up to that point (as I describe in "Using Breakpoints" in this chapter), so that you can step through the program's execution and view the contents of variables. The Debugger window offers even more control over the process and provides detailed information. You can also use the Mini Debugger — a floating window — that offers many of the functions of the Debugger window, as I show later in this chapter.

You can even use the Mac OS X Console utility application to view messages and interact with the GNU Source-Level Debugger with typed commands, as I explain in the "Using the Console" section in this chapter.

Debugging in the Text editor

You can set the Xcode Text editor to recognize breakpoints by clicking the Breakpoints button in the Project window toolbar — the Build and Run button changes to Build and Debug. (I talk more about breakpoints in the "Using Breakpoints" section, later in this chapter.) Click Build and Debug to build and run the program.

A red exclamation point, as shown in Figure 1-6, points to the instruction that caused the program to stop building — that's the Debugger pointing out the problem.

There's even some information about the error. The Debugger offers a strip of information called a datatip, which you can see in Figure 1-8 right underneath the offending line. The datatip says Can not use an object as parameter to a method and ends with a 2, which means another error or warning is there. In Figure 1-7 I click the 2, and it reveals a second warning, Conflicting types.

It turns out that the first message is all I need to know. The compiler thinks I'm trying to use an object (UIApplication) rather than a pointer to an object (UIApplication *) — and that's because I forgot to include an asterisk within the parentheses with UIApplication. The statement should be

- (void)applicationDidFinishLaunching:
                     (UIApplication *)application {
Xcode highlights an error and displays a datatip.

Figure 1-6. Xcode highlights an error and displays a datatip.

The datatip shows a second warning.

Figure 1-7. The datatip shows a second warning.

Tip

When you move your pointer over a variable in a datatip, its contents are revealed, and if more disclosure triangles appear, you can move your pointer over them to see even more information. You can also modify the contents of mutable variables.

Click the up and down arrows next to @implementation DeepThoughts AppDelegate (or whatever you have in your project) in the Debugger strip (refer to Figure 1-6), so that you can see the stack — a trace of the objects and methods that got you to where you are now, as shown in Figure 1-8.

Although the stack isn't really all that useful in this particular context, it can be very useful in a more complex application — it can help you understand the path that you took to get where you are. Seeing how one object sent a message to another object — which sent a message to a third object — can be really helpful, especially if you didn't expect the program flow to work that way.

Getting a look at the stack can also be useful if you're trying to understand how the framework does its job, and in what order messages are sent. As you'll see later in this chapter, using something called a breakpoint can stop the execution of your program at any point and trace the messages sent up to that point.

Looking at the stack in the Editor view.

Figure 1-8. Looking at the stack in the Editor view.

If your app manages to build and run (which can happen even with a warning, as you can see back in Figure 1-4), that means it has passed through the compiler without syntax errors. But you're not out of the woods yet — even if you don't see evidence of runtime errors that crash the app, you certainly haven't tried all the app's functions yet. You also don't know whether there are logic errors. But don't despair; you have options.

The Debugger strip appears just above the Text Editor pane, as shown in Figure 1-9, while the app is running in the iPhone Simulator.

Xcode displays the Debugger strip as the app runs in the iPhone Simulator.

Figure 1-9. Xcode displays the Debugger strip as the app runs in the iPhone Simulator.

The Debugger strip offers several buttons for your pushing pleasure:

  • Thread list: Displays a list of the threads in your program. I explain this in the "Using the Debugger window" section, coming up next.

  • Breakpoints: Activates or deactivates breakpoints, which I describe in the "Using Breakpoints" section later in this chapter.

  • Continue: Continues execution of a paused process in your program.

  • Step Over: Steps over the current line of code. The process counter (PC), which is identified by the red arrow in the gutter, moves to the next line of code to be executed in the current file.

  • Step Into: Steps into a function or method in the current line of code. If possible, the Text editor shows the source file with the called routine. The PC (red arrow) points to the line of code to be executed next.

  • Step Out: Steps out of the current function or method. The Text editor shows the source file with the function's caller.

  • Show Debugger: Opens the Debugger proper.

  • Show Console: Opens the Mac OS X Console, which I describe in the "Using the Console" section in this chapter.

  • Call list: Displays a list of the called functions or methods in the current call stack, which I explain in the "Using the Debugger window" section, coming up really, really soon.

You can play with your app in the iPhone Simulator and then switch back to launch the Debugger window from the Debugger strip.

Using the Debugger window

After clicking the Show Debugger button in the Debugger strip in the Project window, or choosing Run

Using the Debugger window
The Debugger window.

Figure 1-10. The Debugger window.

The Debugger window has everything the Text Editor pane has, but you can also see your stack and the variables in scope at a glance. It also has some extra functionality I show you in the upcoming section "Using Breakpoints."

Here's what you see in the Debugger window:

  • Toolbar: Offers buttons for controlling the program's execution, including Pause/Restart, Continue, Step Over, Step Into, and Step Out. (Restart starts execution from the beginning, whereas Continue continues execution from a breakpoint).

  • Thread list: Shows the call stack of the current thread. For each function or method call that your program makes, the Debugger stores information about it in a stack frame. These stack frames are stored in the call stack. When you pause execution by clicking the Pause button in the Toolbar, Xcode displays the call stack for the currently running process in the Thread list and puts the most recent call at the top. The pop-up menu above this view lets you select different threads to view when debugging a multi-threaded application.

  • Variable list: Shows information — such as name, type, and value — about the variables for the selected stack frame. To see the contents of a structured variable (including arrays and vectors) or an object, click the triangle next to the variable.

  • Text Editor pane: Displays the source code you are debugging. When you pause execution by clicking the Pause button in the Toolbar, the Debugger highlights the line of source code where execution paused and displays the PC red arrow indicator.

  • Status bar: Displays the current status of the debugging session. For example, in Figure 1-10, Xcode indicates that GDB (the GNU Source-Level Debugger) has just been interrupted.

Your window may not look exactly like Figure 1-10 — that's because Xcode gives you lots of different ways to customize the look of the Debugger window. You can, for example, choose Run

The Debugger window.

Tip

You might want to choose Run

The Debugger window.

Using the Mini Debugger

The Mini Debugger is a floating window that provides debugging controls similar to those of the Xcode Text editor. It can make debugging a bit easier, because you don't have to switch back and forth between your running application and your Xcode Project window and Debugging window.

To show the Mini Debugger while running your program, choose Run

Using the Mini Debugger
Use the Mini Debugger to pause program execution and check out the code.

Figure 1-11. Use the Mini Debugger to pause program execution and check out the code.

After pausing or stopping the program (or reaching a breakpoint), the Mini Debugger displays the same information you would see when debugging in the Text editor, as shown in Figure 1-11 (right side). As you can see in Figure 1-11 (right side), you can click the rightmost pop-up menu along the top of the window to see the call stack.

Using the Console

The Console utility application, supplied with Mac OS X, lets you watch error and status messages as they appear. If your computer appears to be stalled or is acting in an unusual manner, the Console might be producing information that can help debug the problem. While the Xcode Debugger provides a graphical interface for GDB (the GNU Source-Level Debugger), the Console lets you interact directly with GDB using a command line. You can type commands using the Console to perform simple debugging tasks, such as displaying the value of something.

For example, I use NSLog statements to log messages to the Console before and after the slider.value statement that tells me the information I need to know:

NSLog(@"Slider value %f parent value %f" ,slider.value ,
   ((MainViewController*)(self.parentViewController)).speed);
  slider.value = + kMaxSpeed - ((MainViewController*)(self.
   parentViewController)).speed;
NSLog(@"Slider value %f parent value %f" ,slider.value ,
   ((MainViewController*)(self.parentViewController)).speed);

To open the Console window, choose Run

Using the Console
Use the Console to monitor messages sent from your code.

Figure 1-12. Use the Console to monitor messages sent from your code.

You can use the Console window to see the commands that Xcode sends to GDB or the Java command-line debugger, to actually send commands directly to GDB or the Java command-line debugger, and to look at the debugger output for those commands. To enter commands, click in the Console window and type at the gdb or JavaBug prompt. To get help with GDB and Java debugging commands, type help. (To get the gdb or JavaBug prompt, the program you're debugging must be paused.)

Using Breakpoints

A breakpoint is an instruction to the Debugger to stop execution at that instruction and wait for further instructions (no pun intended). By setting breakpoints at various methods in your program, you can step through its execution — at the instruction level — to see exactly what it's doing. You can also examine the variables the program is setting and using. If you're stymied by a logic error, setting breakpoints is a great way to break that logjam.

To set a breakpoint in the Xcode Text editor, click inside the far-left column of the Editor pane. In Figure 1-13, I've set two breakpoints — one to stop execution right before the (IBAction)done statement and one to stop execution right after the statement.

Setting two breakpoints in the Text editor.

Figure 1-13. Setting two breakpoints in the Text editor.

Tip

To get rid of a breakpoint, simply drag it off to the side. You can also right-click the breakpoint and choose Remove Breakpoint from the pop-up menu that appears.

When you build and run the program, the Debugger strip appears in the Text editor as the program runs in the iPhone Simulator. The program stops executing at the first breakpoint. The process counter (PC) red arrow points to the line of code in the Text editor immediately following the breakpoint. You can then click the Show Debugger button in the Debugger strip (refer to Figure 1-9), or choose Run

Setting two breakpoints in the Text editor.

In the Debugger window, as shown in Figure 1-14, you can move your pointer over an object or variable in the Text Editor pane to show its contents, and move your pointer over other disclosure triangles to see even more information.

For example, in Figure 1-14, I move the pointer over self to expand the view of self to show FlipsideViewController, and then expand the view of FlipsideViewController to see that NSString (wordsOf Wisdom) is nil, which it should be at this point. In Figure 1-15, I move the pointer over delegate in order to expand the view of self.delegate to show MainViewController, and then expand the view of MainView Controller to see that the value of speed is 10. This is a powerful way to find out the value of variables at any given point during execution.

The Debugger window at the first breakpoint.

Figure 1-14. The Debugger window at the first breakpoint.

Expand the view of self.delegate to show MainView Controller.

Figure 1-15. Expand the view of self.delegate to show MainView Controller.

You can click the Step Into button to go through your code instruction by instruction. The Debugger window also gives you other options for making your way through your program:

  • Step Over gives you the opportunity to skip over an instruction.

  • Step Into takes you step-by-step into a function or method in the current line of code.

  • Step Out takes you out of the current method.

  • Continue tells the program to keep on with its execution.

  • Restart restarts the program. (You were hoping maybe if you tried it again it would work?)

Tip

Expanding the view of objects not only helps you check variables, but also checks messages sent to object reference instance variables. Objective-C, unlike some other languages, allows you to send a message to a nil object without generating a runtime error. If you do that, you should expect to subsequently see some sort of logic error because a message to a nil object simply does nothing. But it's possible that an object reference hasn't been set, and you're sending the message into the ether. If you look at an object reference instance variable and its value is 0x0, any messages to that object are simply ignored. So when you get a logic error, the first thing you may want to check is whether any of the object references you're using have 0x0 as their values, informing you that the reference was never initialized.

As you can see, the Debugger can be really useful when your program isn't doing what you expect. For the blatant errors, the Debugger can show you exactly what is going on when the error occurred. It provides you with a trail of how you got to where you are, highlights the problem instruction, and shows you your application's variables and their values at that point.

What's just as valuable is how the Debugger can help you with logic errors. Sending a message to nil is not uncommon, especially when you're making changes to the user interface and forget to set up an outlet, for example. In such situations, the ability to look at the object references can really help.

Using the Static Analyzer

Xcode offers the Build and Analyze feature (the Static Analyzer) that analyzes your code for memory leaks. The results show up like warnings and errors, with explanations of where and what the issue is. You can also see the flow of control of the (potential) problem.

To show how this works, I deliberately created a memory leak in Deep ThoughtsAppDelegate. In the beginning of Chapter 5 of Book III, you commented out some code because you no longer needed it. I now bring it back just to prove a point.

To make my point, I uncommented the following lines of code in DeepThoughtsAppDelegate.h:

MainViewController *mainViewController;

And a few lines down, I did the same with

@property (nonatomic, retain) MainViewController
   *mainViewController;

Then, in DeepThoughtsAppDelegate.m, I added the following line of code after allocating the other view controllers:

MainViewController *cController = [MainViewController alloc];

Allocating a new object without doing anything with it is sure to cause a memory leak warning.

To run the Static Analyzer, choose Build

Using the Static Analyzer
Potential leak of an object allocated on line 35 and stored
   into 'cController'
The Static Analyzer warns about a memory leak.

Figure 1-16. The Static Analyzer warns about a memory leak.

If you click on the little blue icon for the warning, you get a "trace" of what happened, as I show in Figure 1-17.

The expanded Static Analyzer warning showing a trace of what happened.

Figure 1-17. The expanded Static Analyzer warning showing a trace of what happened.

First you get the following warning, which you can see by moving your pointer over the blue arrow icon in the trace (as shown in Figure 1-17):

Method returns an Objective-C object with a +1 retain
count (owning reference)

Then, in the next line, if you move your pointer over the blue arrow icon, you can see this:

Object allocated on line 35 and stored into 'cController' is
no longer referenced after this point and
has a retain count of +1 (object leaked)

Tip

Notice that the results refer to line numbers. That's why I made a point of explaining how to turn on line numbers in Xcode back in Chapter 4 of Book I.

As you know by now, memory management is a big deal on the iPhone.

Before you attempt to get your app into the App Store or even run it on anyone's iPhone, you need to make sure it's behaving properly. By that I mean not only delivering the promised functionality, but also avoiding the unintentional misuse of iPhone resources. Keep in mind that the iPhone, as cool as it may very well be, is nevertheless somewhat resource-constrained when it comes to memory usage and battery life. Such restraints can have a direct effect on what you can (and can't) do in your application.

Although the Static Analyzer can help you detect memory leaks, the real champ at doing that is Xcode's Instruments application, which also lets you know how your application uses iPhone resources such as the CPU, memory, network, and so on. Not sure what's up with the Instruments application? Then check out the next chapter of this minibook, devoted as it is to revealing its many mysteries.

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

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