Cocoa Programming for Mac OS X - Aaron Hillegass [137]
Chapter 37. Distributing Your App
The time will come when you are ready for your app to leave its nest. You’ve crushed all the bugs you can find and tested for leaks in Instruments. It’s high time your app see the world! In this chapter, we’ll talk about how to use Xcode to prepare your app for life outside the debugger.
Build Configurations
Up until now, we’ve been using debug builds for all our testing. Debug builds contain additional information that enables the debugger to show detailed stack information. Debug builds are generally built with optimization disabled; if you’re building for multiple architectures (32- and 64-bit), the debug build is created only for the development system’s architecture.
These are all great settings for development. They make the debugger more useful, and builds are generated more quickly, but they’re the opposite of what you want in a build that you would release to customers: a release build. In a release build, optimizations are turned up, debugging symbols are stripped (to reduce size and make inspecting the code more difficult), and all the architectures are built.
There is nothing particularly special about the debug and release build configurations. They are simply a convention, and all the settings for these configurations are modifiable within Xcode. You can find the existing build configurations in the project editor, on the Info pane. You can also add new build configurations there.
Xcode has several actions available: run, profile, analyze, and archive. A build configuration is associated with each of these actions. You can configure this using the Scheme Editor to associate a build configuration with a particular action (Figure 37.1). That build configuration will be used when building the target for that particular action.
Figure 37.1. The Scheme Editor with the Debug Build Configuration Selected for Run
Preprocessor Macros and Using Build Configurations to Change Behavior
One common use of build configurations is as a means for hardcoding behavioral settings in your application. This is done using preprocessor macros.
In Chapter 3, we saw how to use the NS_BLOCK_ASSERTIONS macro to disable NSAssert. Open a project and click on the project in the project browser. Select the target, and under Build Settings find the Preprocessor Macros line (Figure 37.2).
Figure 37.2. Preprocessor Macros
Note that Preprocessor Macros can be expanded to show Debug and Release. This allows you to define unique sets of preprocessor macros for debug and release builds. In this example, the only macro defined is DEBUG=1 in the debug configuration. This sets the DEBUG macro’s value to 1. To check the value of DEBUG in code, we can do something like this:
#if DEBUG
[self printOutEverything];
#else
[self printOnlyWhatsNeeded];
#endif
If you’re not familiar with preprocessor macros, the concept is fairly simple: Before your source code is compiled, it is run through the preprocessor, which processes your source code on a line-by-line basis. In our case, the code sent to the compiler in a debug build would be:
[self printOutEverything];
Note that the other code is completely omitted: There isn’t even a branch to be evaluated when the program runs. You can, however, use macros in branches if you wish. For example:
if (DEBUG) {
NSLog(@"Debug is %d", DEBUG);
}
One last note about preprocessor macros: They can be used to do a lot more than simply defining functions. You might want to log only certain statements in your debug build. Our first thought might be something like this:
#if DEBUG
NSLog(@"This happened.");
#endif
But it would be much less obtrusive to simply use:
DebugLog(@"This happened.");
We could implement this by defining a DebugLog function. A preprocessor macro allows us to erase the logging code completely, however. The following macro gives us a DebugLog macro, which looks just like a function but evaluates to nothing when DEBUG is zero:
#if DEBUG
#define DebugLog(...)