Managing iOS build configurations

Dmitry Fink
August 31, 2016

Why?

Applications today are rarely being built without 3rd party libraries and SDKs. There are libraries for integrating remote backends into your app. Libraries for effective image caching and loading. Libraries for gathering analytics and libraries for pushing messages. Libraries that help your users report issues, help you debug and analyze crashes. (Yes, that last one is Bugsee)

It is also a common practice for developers to maintain more than flavor of the application during active development. A debug version of the application may require a new version of the backend server which also might be under development still, it might send analytics data to a different service or it may need to include a helper library that is not necessary in the release version.

This following tutorial describes several options for maintaining different build configurations.

We are using Bugsee SDK as an example in this tutorial. At the same time, we do want to point out, that Bugsee SDK is extremely lightweight and doesn't impact your user's experience. Bugsee also passes all Apple Appstore requirements. A lot of Bugsee's customers have seen the benefits of shipping the library in their App Store builds, it provides them with video-enabled crash reporting and in-app bug reporting.

Debug vs. Release

This one is the default most common setup you get when you create your application with xCode. With preprocessor macros it is easy to differentiate at compile time between a Debug and a Release build. Thus, it is easy to launch (or not launch) a library in any particular configuration. Lets launch Bugsee only in Debug build.

#if DEBUG
#import "Bugsee/Bugsee.h"
#endif
....
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // ...other initialization code
#if DEBUG 
    [Bugsee launchWithToken:@"your app token"];
#endif

So now you don't import and don't run it, but the framework is still getting packaged into your application. If you use CocoaPods for you library management, there is a solution, starting with v0.34, CocoaPods honors build configurations. All you have to do is put the following in your Podfile:

pod 'Bugsee', :configurations => ['Debug']

Unfortunately, if you manually copy libraries into your project, you are out of luck.

Debug vs. Release (+TestFlight)

In this configuration you still manage and maintain two build flavors (Debug and Release). The Release build is first distributed to QA and Beta testers through Apple TestFlight and only when ready you promote it to App Store. The beauty of this approach is that it is the same build(same binary!). Thus, you can be sure there are no differences between what was tested and what was released. This means, however, that Bugsee SDK must be part of that Release build and you just want to disable it when it is being installed from App Store.

Fortunately, it is easy to differentiate between App Store and Testflight installations in run-time and initialize Bugsee only when appropriate:

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // ...other initialization code
    if ([[[[NSBundle mainBundle] appStoreReceiptURL] lastPathComponent] isEqualToString:@"sandboxReceipt"]) {
        // We are in TestFlight, enable Bugsee!
        [Bugsee launchWithToken:@"your app token"];
    }
    return YES;
}

Debug vs. Release vs. Beta

In this setup you create and maintain three build flavors. Distribution method of the Beta flavor is not important, it can be done either through Ad-hoc distribution (see our tutorial on building in house Ad-hoc distribution using S3) or through TestFlight. In this setup you are back to detecting the configuration at compile. Adding an additional configuration, however, is a multi step process:

Go to your project Info, and under Configurations, create a new one by duplicating the Release one.

Go to Build Settings and in Preprocessor Macros and add BETA=1 to that new Beta configuration (note that this is actually the place DEBUG=1 is being set for that Debug build).

Voila! You can now detect Release versus Beta build at compilation time. Other than the BETA flag, the builds are identical.

Let's launch Bugsee in Debug and Beta only:

#if ( DEBUG || BETA)
#import "Bugsee/Bugsee.h"
#endif
....
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // ...other initialization code
#if (DEBUG || BETA)
    [Bugsee launchWithToken:@"your app token"];
#endif

Let's use that CocoaPods trick to install the library only in Debug and Beta builds:

pod 'Bugsee', :configurations => ['Debug', 'Beta']

The only thing left to do is to build the Beta configuration.

First of all, go to Product->Schemes->Edit Scheme and click on Duplicate Scheme button. Name it properly (if your original scheme was called MyApp, call the new one MyApp Beta).

Edit the new scheme, and change the configuration of the Archive step to use Beta configuration.

That's it. Now you have two schemes, one for Appstore and one for Beta releases. As long as you are developing and using Build & Run, they are identical, and it doesn't matter which one you are using. It matters though when you do Archive, depending whether you want to produce and IPA for App Store or Ad-Hoc, remember to switch to the right one.