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
Sounds overblown, right? but – 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?
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.