The Debugger
The Free Software Foundation developed the compiler (gcc) and the debugger (gdb) that come with Apple’s developer tools. Apple has made significant improvements to both over the years. This section discusses the processes of setting breakpoints, invoking the debugger, and browsing the values of variables.
While browsing code, you may have noticed a gray margin to the left of your code. If you click in that margin, a breakpoint will be added at the corresponding line. Add a breakpoint in main.m at the following line (Figure 3.10):
[array addObject:newEntry];
Figure 3.10. Creating a Breakpoint
When you run the program, Xcode will start the program in the debugger if you have any breakpoints. To test this, run it now. The debugger will take a few seconds to get started, and then it will run your program until it hits the breakpoint.
When your application is running, the debugger bar will be shown below the editor area. The debugger bar contains a button to toggle visibility of the full debugger area, including the variables view and console, as well as buttons to control the execution of your program and information about the current thread and function.
Xcode’s default behavior is to show the full debugger area when a breakpoint is hit. If you do not see the debugger area at the bottom of the window, use the debugger area view toggle in the debugger bar (or toolbar), or the View->Show Debugger Area menu item.
You should also see the Debug navigator on the left, which shows the threads in our application and frames on the stack for each thread. Because the breakpoint is in main(), the stack is not very deep. In the variables view on the left in the debugger area, you can see the variables and their values (Figure 3.11).
Figure 3.11. Stopped at a Breakpoint
Note that the variable i is currently 0.
Return your attention to the debugger bar. Four of the buttons above the variables view are for pausing (or continuing) and stepping over, into, and out of functions. Click the Continue button to execute another iteration of the loop. Click the Step-Over button to walk through the code line by line.
The gdb debugger, being a Unix thing, was designed to be run from a terminal. When execution is paused, the gdb terminal will appear in the Console panel.
In the debug console, you have full access to all of gdb’s capabilities. One very handy feature is “print-object” (po). If a variable is a pointer to an object, when you po it, the object is sent the message description, and the result is printed in the console. Try printing the newEntry variable.
po newEntry
You should see the result of your description method (Figure 3.12).
Figure 3.12. Using the gdb Console
Exceptions are raised when something goes very wrong. To make the debugger stop whenever an exception is thrown, you will want to add an exception breakpoint. Click the Add button at the bottom of the breakpoint navigator and select Add Exception Breakpoint.... Set the exception type to Objective-C and click Done (Figure 3.13). Disable the existing breakpoint in main() by clicking on the blue breakpoint icon in the breakpoint navigator. The breakpoint will be dimmed when it is disabled.
Figure 3.13. Adding an Exception Breakpoint
You can test this exception breakpoint by asking for an index that is not in an array. Immediately after the array is created, ask it what its first object is:
array = [[NSMutableArray alloc] init]; NSLog(@"first item = %@", [array objectAtIndex:0]);
Rebuild and restart the program. It should stop when the exception is raised.
One of the challenging things about debugging Cocoa programs is that they will often limp along in a broken state for quite a while. Using the macro NSAssert(), you can get the program to throw an exception as soon as the train leaves the track. For example, in setEntryDate:, you might want an exception thrown if the argument is nil. Add a call to NSAssert():
- (id)initWithEntryDate:(NSDate *)theDate { self = [super init]; if (self) { NSAssert(theDate != nil, @"Argument must be non-nil"); entryDate = theDate; firstNumber = ((int)random() % 100) + 1; secondNumber = ((int)random() % 100) + 1; } return self; }
Build it and run it. Your code, being correct, will not throw an exception. So change the assertion to something incorrect:
NSAssert(theDate == nil, @"Argument must be non-nil");
Now build and run your application. Note that a message, including the name of the class and method, is logged and an exception is thrown. Wise use of NSAssert() can help you hunt down bugs much more quickly.
You probably do not need your assert calls checked in your completed product. On most projects, there are two build configurations: Debug and Release. In the Debug version, you will want all your asserts checked. In the Release configuration, you will not. You will typically block assertion checking in the Release configuration (Figure 3.14).
Figure 3.14. Disabling Assertion Checking
To do this, bring up the build settings by selecting the lottery project in the project navigator (topmost item). Then select the lottery target, change to the Build Settings tab, and find the Preprocessor Macros item. A quick way to find it is to use the search field at the top of the Build Settings panel. The Preprocessor Macros item will have one item beneath it for each build configuration: Debug and Release. Set the Release item value to NS_BLOCK_ASSERTIONS.
Now, if you build and run the Release configuration, you’ll see that your assertion is not getting checked. (Before going on, fix your assertion: It should ensure that dates are not nil.)
You can change your current build configuration to Release by opening the scheme editor (in the Product menu, click Edit Scheme...). Select the Run action; on the Info panel, change Build Configuration to Release. Now when you build and run your application, it will be built using the Release configuration. Note that the default build configuration for the Archive action is Release. We will discuss build configurations in more detail in Chapter 37.
NSAssert() works only inside Objective-C methods. If you need to check an assertion in a C function, use NSCAssert().
That’s enough to get you started with the debugger. For more in-depth information, refer to the documentation from the Free Software Foundation (www.gnu.org/).