Ok, I admit it – my favorite debugging tool is still
(and print statements in general). In this post I’ll mention some useful debug logging techniques you can use with the help of preprocessor macros.
Logging method calls
It’s as simple as this:
#define PrintName NSLog(@"%s", __FUNCTION__)
Add this macro (with a semicolon) to the start of a method, and you’ll see its name on the debug console every time the method is called. I won’t go into detail about how useful this can be, but to skim the surface: You can use this trick to help isolate the conditions leading up to a tricky crash, you can get clues toward which pieces of the code are the bottlenecks, and you can sanity check that methods you want to be called are indeed being called. There are other ways to do these things, but this is a very simple and easy-to-use approach.
It works because gcc understands __FUNCTION__ as a special compiler-defined variable that acts as a c string with the currently executing function name. Several other folks mention that __PRETTY_FUNCTION__ is also available, although in Objective-C it gives an identical string (the difference is only in C++, where __PRETTY_FUNCTION__ includes more information). [Reference]
You may also find it useful to use
, which is a c string with the name of the current file, and
, which is an integer for the current line in the current file. Like
, these are defined by gcc, and these latest two are treated as preprocessor macros (constants). [Reference]
Your own version of NSLog
It can sometimes be useful to prepend every log statement you make with information about where that line was generated in the code. One way to do this is:
#define PrintNameLog(x, ...) NSLog(@"%s " x, __FUNCTION__, ##__VA_ARGS__)
You can use this macro in a manner identical to
. The difference in behavior is that your log statement will start with the name of the method it was called from. You can easily replace @”%s ” by @”%s %d ” and __FUNCTION__ by __FILE__, __LINE__ (and similar replacements) to get different information per log line.
This macro relies on two non-obvious compiler properties: (1) That if you use two string literals in a row, the compiler concatenates them for you – this is how the
becomes a single parameter for NSLog (the compiler will complain if x is not a string literal, but it would also complain if you gave a non-string-literal to NSLog). And, (2), the behavior of the double-hash token after a comma in a macro definition. The
part is always replaced by whatever inputs are included by the ellipses (the dots…), but what if there is no extra input? Then the macro definition ends in a comma that would result in a compiler error. Double-hash to the rescue! This token tells the preprocessor to delete the previous comma if there is no extra input. Everyone is happy.
Excluding debug output in non-debug builds
One bad thing about extensive use of
is its speed – or lack thereof. It actually gives your code a serious performance hit when called often. Several times I’ve used log statements to help judge the speed of some section of my code only to find the real bottleneck was the log statements themselves.
In short, you really want to leave out your debug log statements in code for release/distribution.
You could do this by hand, but that’s not the coder way. Instead, you can use the traditional DEBUG macro to detect when we’re debug mode. Something like this:
#ifdef DEBUG #define MyLog(x, ...) NSLog(@"%s " x, __FUNCTION__, ##__VA_ARGS__) #else #define MyLog(x) #endif
Now your debug statements magically disappear when you run a non-debug build… almost. The trouble is that
is not defined by default in xcode in any build mode, so you have to add it. Here are the steps to do that:
- Right-click on your project icon at the upper-left of the Groups & Files pane on the left side of xcode.
- Choose Get Info
- Click on the “Build” tab (between General and Configurations, at the top)
- Choose Debug configuration, and show All Settings
- Scroll down to the section “GCC 4.2 – Preprocessing” OR type “preprocessor” in the search box
- Find the key called “Preprocessor Macros” (this is the human-friendly version of GCC_PREPROCESSOR_DEFINITIONS)
- In the value column for that key, type in “DEBUG”
That’s it! Just seven fairly obvious steps there.
Similar techniques have been pointed out several times before.