Debugging



Relevant resources


Documentation


Web


Video - Apple


Video - others


Podcasts


Sample application



For the very rare cases when it's not your fault


File bug reports / enhancement requests at https://bugreport.apple.com


Compiler errors


Again, I highly recommend turning on the build setting to treat all warnings as errors.  This will let you catch problems early, rather than at runtime.


Errors in Xcode 3.2 are nicely highlighted and indicated in the scroll bar.


Messes of errors about undefined symbols typically mean that you have forgotten to include appropriate headers.  Likewise, all sorts of linker errors about symbols usually mean that you have forgotten to link the correct frameworks, or that the framework you have chosen is for the wrong architecture (Intel vs. ARM, Mac vs. iPhone).


Clang static analyzer


Another compile-time layer of error checking can be done using the Clang Static Analyzer.  The Clang Static Analyzer is a byproduct of Apple's efforts to move beyond GCC as a compiler and parser to the new LLVM compiler and Clang parser.  Both are BSD-licensed, unlike GCC, which allows Apple to be more flexible in their use within the closed-source development tools they make.  Both also have been architected in a fascinating way, and they compile applications much faster than GCC, as well as produce faster applications as a result.  Unfortunately, the Clang / LLVM team are not yet used for the iPhone (only the Mac on Snow Leopard).


However, we can take advantage of the Clang Static Analyzer for our iPhone projects.  Before Xcode 3.2, you had to download and set up the analyzer manually, then run it at the command line to generate an HTML report of suspected problems in your code.  In Xcode 3.2, all you have to do is choose Build and Analyze from the Xcode menu.  You can even have the analyzer run on every build by selecting the Run Static Analyzer option.  I do this for all of my projects.


The static analyzer will parse your code and find what it believes are bugs by following paths through your code.  It can evaluate all possible tracks through a series of branching statements, finding obscure edge cases that you may only run into 1 in a million times.  Because it knows so much about the functioning of your code, it is particularly good at hunting down memory leaks or overreleases of objects.  It can also catch uninitialized values and dead stores.


When it finds a bug, it provides neat graphical feedback as to the execution path it took to find that bug.


Assertions


One of the most common problems with interfaces laid out in Interface Builder is missed connections between your IBOutlets and your interface elements.  You get no warning that something is unassigned, and your application doesn't crash if someone clicks on an unattached button.  Your operation just fails silently.


One way to protect against this is to use assertions.  Assertions kill your application immediately if their argument is not true.  For example,


assert(myTextField != nil);


will stop your application at that line if myTextField is nil.  If you used an assertion like this for every one of your Interface Builder outlets in the initialization phase of a view controller, you'd know for certain if you screwed up and forgot to connect an outlet.


This is one case where it's good to force a crash, because it prevents a much more difficult to debug problem from happening.


Logging to the console


The simplest form of debugging, that almost all programmers use at some point, is the log statement.  In Cocoa, you use NSLog() for this.  NSLog() takes in an NSString format string and outputs the processed result to the console (viewable while debugging using Run | Console), along with a timestamp and process information.  For example, the following


int foo = 1;

NSLog(@"This is a test.  The number is %d.", foo);


will produce output like


2010-01-26 10:45:05.053 MyApp[6444:207] This is a test.  The number is 1.


For more information on format strings, please see the "String Format Specifiers" section of the String Programming Guide for Cocoa. 


It is considered bad form to leave logging code in production builds, especially on the iPhone.  NSLog() is also pretty slow, so it can have a significant effect on your application's performance.  When done debugging, be sure to strip out all instances of NSLog() in your code, or use the following macro:


#ifdef DEBUG_MODE

    #define DebugLog( s, ... ) NSLog( @"<%p %@:(%d)> %@", self, [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__] )

#else

    #define DebugLog( s, ... ) 

#endif


to define a DebugLog() function which will only log to the console for debug builds (ones where you've set DEBUG_MODE in the  Preprocessor Macros section of the build settings for that target).  This code is drawn from Marc Charbonneau's response to this discussion on Stack Overflow.


When an object is printed to the console using the %@ format specifier, its description method is called to generate an NSString representation of its contents.  By default, this method outputs some generic pointer 


Crash logs


When you build your application for release to the App Store, there will be another file created in your build directory.  This file will have the same name as your application, but with the .dSYM extension.  The .dSYM files contain the debugging symbols that have been stripped out of your release build for your application.  Store this file in a safe location, based on the version of the application.  Do not overwrite earlier .dSYM files for previous versions of your application.


After your application has been in the App Store for a little while, you will have access to crash logs from within iTunes Connect.  They will be ranked based on their overall frequency, and similar crashes will be grouped together.  If you download these crash reports, you can unzip them and double-click on the crash logs themselves to bring them into the Console application.  If you've saved the .dSYM file for this particular version, and have that .dSYM accessible to Spotlight searches, the names of your classes and methods should be symbolicated.  Otherwise, you'll just see a bunch of memory addresses for any parts of a stack trace that take place in your own code.  This is why you need to hang on the the .dSYM files for each release.

 

Using the debugger


There are several different ways to interact with the debugger.  My personal favorite is the full debugger window, which can be accessed using Run | Debugger.  The full debugger window includes a stack trace, a list of current local variables, and a source code window that highlights where execution has stopped at that given moment.


Before you do anything else, there is one item you must set.  Open the window that lists breakpoints by choosing Run | Show | Breakpoints.  Within that window, select Global Breakpoints in the left-hand column.  On the right, add a new breakpoint for objc_exception_throw with a location of libobjc.A.dylib.  What this will do is cause the debugger to stop at the exact location an exception is thrown.  Because exceptions are so common in Cocoa as causes of program halts, it's a good idea to have this as a global breakpoint across all projects.


Breakpoints are places in the code that you've set where execution will halt when you're running the debugger against your application.  They can be used to monitor the control flow of your application, or to determine what the values are of a series of variables at a given point in the execution of your application.  To set one in your application, simply click in the gutter to the left of your code at the line where you'd like to stop.  To remove a breakpoint, drag it out of the gutter.


If your application is crashing, the first thing to do is start it with the debugger attached.  No matter if your application is running on the device or in the simulator, you can do this by choosing Run | Debug - Breakpoints On.  You can also enable breakpoints while your application is still running by clicking on the appropriate toolbar icon in the debugger.


When your application crashes due to a message to a released object or similar problem, the debugger will catch the error and halt your application at the point where this happened.  You should be able to look at the stack trace on the left and pick out the method of yours where things halted.  Clicking on that method in the trace should switch to the correct location in your code and highlight the line which caused the error.


This is why you want to set a breakpoint on exceptions being thrown, because if you do so the debugger will also halt at the location in your code that caused the exception.


When execution is halted, you can browse the values of local variables by mousing over them.  For structs or objects, you can even navigate the hierarchy of values stored within those data types.


You can print out the value of a variable by typing in the Console.  The command 


print counter


will print the value of the scalar value counter.  To do the same for an object, use 


po myString


You can even evaluate Objective-C methods in the console.  For example, 


print  (int)[theObject retainCount]


will print out the retain count of theObject and 


po [myArray objectAtIndex:0]


will output the object at index 0 in your array.


I refer you to TN2124: Debugging Magic for much more that you can do using the console to talk to the GDB debugger.


-Data formatters


-Setting environment variables 


{(NSString *)[$VAR valueForKey:@\"name\"]}:s