Xcode Build Settings Part 1: Preprocessing

Xcode Build Settings Part 1: Preprocessing

That screen. You know the one. The table of text. The one with scary sounding terms such as Mach-O Type and Rez Search Paths. The one you’re probably avoiding right now, for fear that changing one of the magic incantations will leave you with an empty husk of an app powered only by sadness and regret. There’s actually a whole ton of useful stuff which you probably didn’t know about. In this multi-part article, we’ll go over some of the fun possibilities once you understand the available settings.

Disclaimer (no seriously read this)

I know I said this wasn’t that scary. And I stick by that. But there’s one thing to be aware of. You can’t actually undo any changes you make (and might later regret). So please please please make sure you are working in a clean repo before playing around with settings you aren’t familiar with so that you can easily reset in case something blows up on you.

With that out of the way, let’s go poke it with a stick, shall we?

2001-Monolith

Compiler flags

The first thing we’re going to play around with is one of the least likely to blow up in your face. Compiler flags are used to define constants at build time, that can then be used in your code to do some tricky things to customize your code for specific build configurations.

There are 3 possible places to set these up:

  • OTHER_CFLAGS (Other C Flags)
  • GCC_PREPROCESSOR_DEFINITIONS (Preprocessor Macros)
  • INFOPLIST_PREPROCESSOR_DEFINITIONS (Info.plist Preprocessor Definitions)

The difference between the Other C Flags setting and the two Preprocessor settings is that anything you pass into Other C Flags is passed directly to the compiler as is. This means that if you want to set up a constant named FOO, you would have to format the C Flag as -DFOO. This gets passed as is to the compiler, and defines the FOO constant. However, this also means that a malformed C Flag could potentially blow up your build.

Conversely, anything passed in through one of the Preprocessor settings is passed to the compiler with -D automatically. So that same constant from before can simply be set up as FOO. This means that even if you write a malformed flag, you will only get a malformed definition in return. Because of this, I recommend sticking to the Preprocessor macros settings for defining compiler flags, leaving the Other C Flags setting for times when you actually want to pass flags directly to the compiler.

Compiler flags can be set either as a value definition (FOO=1), or a constant definition (FOO). Constant definitions are essentially boolean. They are either set, or not set. Value definitions can have value, but you really don’t want the compiler going through complex conditionals to generate your code. If you want to use a value definition, stick to setting the flag to 1 and simply checking for its existence.

So how do you use this? The most common use is dynamically swapping out sections of code based on the build configurations. Once you have your flags set up, you can do some slick dynamic compiling in your code. As an example, you could swap out an API endpoint based on the build configuration like so:

#if RELEASE
static NSString *const MY_API_URI = @"https://api.example.com/";
#else
static NSString *const MY_API_URI = @"https://api.staging-example.com/";
#endif

Remember Info.plist Preprocessor Definitions? These can be used in conjunction with the INFOPLIST_PREPROCESS (Preprocess Info.plist File) flag to do the same kind of dynamic compilation for your Info.plist file. The simplest use of this technique is to swap out your bundle identifiers and modify your product names so that you can distinguish beta builds from release builds, and be able to keep both installed at the same time. Right click on your Info.plist file, and choose Open As -> Source Code. You should now see the plist file in its raw XML. Now look for the sections you want to modify, and use the same kind of compiler conditionals used earlier.

<key>CFBundleDisplayName</key>
#if RELEASE
<string>${PRODUCT_NAME}</string>
#else
<string>${PRODUCT_NAME} Beta</string>
#endif
<key>CFBundleIdentifier</key>
#if RELEASE
<string>com.yourcompany.myapp-appstore</string>
#else
<string>com.yourcompany.myapp-beta</string>
#endif

Once you start adding compiler conditionals into your Info.plist, Xcode will start to tell you that the file has been corrupted, and is unreadable. Don’t worry, it isn’t. You can always do the same right click -> Open As -> Source Code tango you used to add the compiler conditionals in the first place. The one issue this may cause is that the “Summary” screen in newer versions of Xcode will become unable to read your Info.plist file. This means that if you want to change any of the settings contained within the file, you will have to edit the XML directly, which isn’t always the most pleasant of experiences.

Bonus round

There are actually some built in flags you can use to help compile your source code dynamically. The most interesting ones for our purposes are TARGET_IPHONE_SIMULATOR and TARGET_OS_IPHONE. These are set as value definitions, so you should be using #if to check the conditional, not #ifdef. This can be used in all sorts of interesting ways. My favorite use-case is an extension of the dynamic API constant from earlier. When developing against an API for a rails app for which I have access to the source code I prefer to run the app locally, instead of dealing with calls to a staging server. So I take the example above, and modify it like so:

#if TARGET_IPHONE_SIMULATOR
static NSString *const MY_API_URI = @"http://localhost:3000/";
#elif RELEASE
static NSString *const MY_API_URI = @"https://api.example.com/";
#else
static NSString *const MY_API_URI = @"https://api.staging-example.com/";
#endif

Now, when building for the simulator, I’m pointing the app at my local Rails server. But the app is still pointing at the real API for release builds, and the staging API for everything else. Huge time saver.

In addition, there are a number of variables you can use inside your compiler flags to create the flags dynamically. Notably, the CONFIGURATION variable corresponds to the build configuration name. That means that setting up a preprocessor macro with CONFIGURATION_$(CONFIGURATION) for all build settings will resolve to CONFIGURATION_Debug for builds under the ‘Debug’ build setting, but will resolve to CONFIGURATION_Release for builds under the ‘Release’ setting. [HockeyApp][http://www.hockeyapp.net] recommends using this same technique to keep their Beta Testing code out of your release builds for the app store.

With great power…

I’d be remiss if I didn’t state that this technique should be used extremely sparingly. The use cases presented here are examples of how you may want to use source code preprocessing, but you can quickly go too far. Used correctly and sensibly however, Preprocessors can be a powerful addition to your workflow. You just have to know where to find them.

Gordon Fontenot Developer