Build configurations in iOS development allow us to create specifications about how we want our app to be built. This makes it easy to compile an app for a particular environment (test, development, release, etc). Changing property values and executing code conditionally, depending on the environment, becomes essentially automatic. We’ll go through the steps to set this up below.
First, go into the Xcode project file settings. From there, make sure you have the project itself selected (not any targets). You will see two existing configurations which are present in any new project you create, “Debug” and “Release”.
You can just use these if you’d like, or you can rename or remove them. You’ll need to have at least one configuration though. To add a new one, click the “+” button and select which existing configuration you would like to duplicate.
I typically have “Release”, “Test” and “Development” configurations in my small-to-medium projects. Most apps I’ve worked on have different API URLs, depending on whether the app is being deployed locally by the developer, is being tested, or is released on the app store. Having different configurations for each makes it easy to deal with those variations.
Setting Flags That Are Accessible in Code
Now that we have build configurations set up, we can add what are called Active Compilation Conditions in the project build settings. Doing this will create flags at compile time which we can access in code so that we’re able to make changes based on the app environment. To set these flags, go to build settings for the target, and locate the Active Compilation Conditions section under Swift Compiler – Custom Flags. For each of the build settings listed, add a text flag. The convention on naming these is to use all caps.
Creating Build Schemes
The next step is to create build schemes for each of the build configurations we now have. In the top bar of Xcode, click on on the build scheme (this will be the name of the project, right next to the device selection). Then click on Manage Schemes. From there, click on the “+” button at the bottom of the view and add a new scheme for each build configuration. You can keep the existing one and rename it if you’d like.
Edit each build scheme you created and, in the Info tab, set the corresponding build configuration. I normally do this for each part of the scheme (e.g. Build, Run, Test, etc) to ensure consistency.
Using the Configurations in Code
Now for the really useful part! In code, we can access the flags we created in the build settings like so:
#if DEVELOPMENT
let baseApiUrl = "https://dev.myapiurl.com/dev/"
#elseif TEST
let baseApiUrl = "https://test.myapiurl.com/"
#elseif RELEASE
let baseApiUrl = "https://myapiurl.com/"
#endif
baseApiUrl.makeRequest(...)
When the app is actually compiled, the correct baseApiUrl
will be used, based on which build scheme is selected in the top bar of Xcode. You may notice that if
statement is not written in Swift. This syntax creates what’s called a “compiler directive”, and is not actually executed until app compilation.
There are many different use cases for these flags. You could even make a global property of isRelease
which makes it easy to check throughout your app:
#if RELEASE
let isRelease = true
#else
let isRelease = false
#endif
It may be useful to have a custom “debug” print statement that only prints when the app is not in a release configuration. This is something you may want to use for security reasons, such as to avoid logging credentials in production code.
func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {
if !isRelease {
Swift.print(items[0], separator:separator, terminator: terminator)
}
}
The possible uses of build configuration flags are endless! I’ve found them to be a must in nearly every project I’ve worked on. They make many environment-based changes much more efficient.