Conditional compiling benefits to tracing and debugging your application
Printing logs (traces) in the console is a common way for us -developpers- to track and debug our code. You always find yourself adding a NSLog(@”I’m in this block”); at some point just so you understand what’s happening in your code when you face a bug or when you want to track what’s going on in your app. These traces generally have little interest for your end users, that’s why they’re printed in the console. While this practice is not bad, it adds a little overload to your application, and the little overload multiplied by dozens or hundreds of traces may finally end up as a significant amount of compute time, especially when you’re working on large projects and printing large amount of data. This is because I/O operations are among the most time-consuming operations a device can perform.
While you may think it’s not really important to optimize your code down at the logging level, you should consider that any optimization, as little as it can be, is important. It is important because you know you can do better, but also because at the end, the user will be the one judging your app. By adding little optimizations to little optimizations you finally endup with a code running better, which will somehow be seen or felt by the user at some point.
The good news is that it is not necessary for you to rush on your project right now to remove all your NSLogs because there’s a great feature your compiler come with named the conditional compilation.
With conditional compilation you have a great tool to define which piece of code should appear or not in your target when you compile the code. Conditional compilation is part of the compiler instructions (the ones starting with a #, like #import). In this case we’ll use #ifdef, #ifndef, #endif, along with #define and #undef.
#define DEBUG #ifdef DEBUG NSLog(@"Print a trace"); #endif
This small code says that if the DEBUG variable is defined (which is the case), the precompiler will include the NSLog() instruction in the final code to be compiled by the compiler. Otherwise it won’t.
Here we’re defining the DEBUG variable with the #define instruction. For it to have effect, you have to place it somewhere in your code before the #ifdef is executed. Later on in your code you can undefine the variable with a simple #undef DEBUG. See? It’s easy! You can have your #define instruction placed in a separate constant file and comment/uncomment the instruction at your will.
But it’s not ideal. First you have to wrap every single NSLog() instruction around a #ifdef #endif block and then you have to comment/uncomment your #define instruction depending on which type of build you want to make.
- To solve the first problem, it’s easy, you just have to put your NSLog in a separate method or C function.
- (void) log:(NSString*)message { #ifdef DEBUG NSLog(@"%@", message); #endif }Now you just have to call [self log:@"Print a trace"] elsewhere in your class. This is not perfect as your method takes only one argument, a NSString, and is callable only in the class which contains the log: selector. There are solutions to this particular defect, it will be covered in another article.
- For the second optimization, it is possible to define your precompiler variable using the compiler command line (the -Dvariable_name options). And for this XCode comes with configurations in which it’s easy to define these variables. In XCode, you can create different configurations for different release types, and you can create as many configurations as you wish. The menu available on the top left of your main XCode window allows you to specify which configuration you want to use for your build. By default there are the Debug and Release configurations.
To edit a configuration, just hit Cmd+I on your project. On the property window, there’s the Configurations tab which allows you to create, delete, duplicate configurations. In the Build tab, you have access to a lot of options in relation to building your project.
In the Build tab, make sure to select the Debug configuration and not All configurations, then in the search field, enter «Other C Flags». At this point, the field is empty, just add the -DDEBUG value (the field «Other C++ Flags» will be automatically populated). And now you’re done!
When writing and debugging your project, select your Debug configuration. When preparing an adhoc build or building the binary for submission to the appstore, just select a different configuration and the NSLogs won’t appear in the console, they even won’t be compiled in your code!
Defining variables for conditional compilations, used along with configurations in XCode, is a convenient way for creating slightly different builds of the same code: you can have debug traces and blocks of codes in your debug configuration while having a clean code for your releases, all within the same project. All one-click away!





If you care about performance, the best would be to use a preprocessor macro. This will prevent allocating the string in memory as well as the overhead of function calls in Objective-C.
You can also use other preprocessor macros to have some nice debug logs
#ifdef DEBUG
#define LOG(xx, …) NSLog(@”%s(%d): ” xx, __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define LOG(xx, …) ((void)0)
#endif
Thanks Yann. This will be covered in one of my upcoming articles.
Hi! Is it ok to use these information in my prject? thanks!
Of course feel free to use it