Managing Multiple Schemes and Build Configurations

Categories:

I will confess, getting a mental grip on XCode schemes and build configurations took a bit of time. In the end I came to use schemes as managing the different types of builds I may want to generate at any given time. For example, the iAchieved.it Lewis and Clark app has five schemes:

  • adhoc production build
  • adhoc development build
  • debug production build
  • debug development build
  • release

Why all these builds?  Let’s break it down.

We have two distinct operating environments in “the cloud” (everything is in the cloud these days), one labeled as production and one labeled as development.  The production environment is carrying “live” traffic from our user community, and has specific webservice endpoints that are exposed to our iOS app.  These endpoints are distinctly different from those that are available in the development environment.  In fact, the development environment is a separate set of servers and applications, and often the development environment is running the latest development code.

Depending on the context we may want to build Lewis and Clark against the production or development environment.  Bleeding edge feature development typically happens on the (you guessed it) development servers.  Features or bug fixes that don’t require server-side changes are often tested against the existing production servers.  It is handy to be able to compile for different environments simply by flipping a switch (or in this case, scheme).

But wait, there’s more!  Any seasoned software developer knows that you should turn off or minimize excessive logging when ready to ship your application, but often times turning off logging can introduce subtle race conditions that you may have not noticed before.  Thus, even during the test phase it is beneficial to test with logging both on and off.  Or, perhaps, you are have been doing testing with the production-level app and your logs have sensitive information in them such as API keys.  You are getting ready to send the application out to your freelance testers and don’t necessarily want them to be able to access your log data.  Rather than changing any code, simply create a scheme that doesn’t include logging (we call it an adhoc scheme).

As the old saying goes, there are a number of ways to skin a cat, and there are countless ways to organize your schemes and build configurations, and you don’t have to have a one-to-one relationship between scheme and build, but this is what has worked for us.  We’re going to create an application that utilizes the same concepts and also casually toss in some techniques for accessing webservice data.

Our app is going to be called darkskyClient and it’s going to make use of the JSON API for obtaining storm data through DarkSky.  You’ll need to register for an API key at https://developer.darkskyapp.com/register, so go do that now.  Once you have your API key, go ahead and create a single-view iOS application, and also add the Lumberjack logging framework.  See our blog post on Lumberjack for step-by-step details on how to add Lumberjack logging to your project.

Here’s a power tip when working with XCode projects: if you already have a project open with files you want to add to your new project, simply create a New Group in the file explorer (left-pane) and then select, drag, and drop the files from one project into your new project, placing them in the new group you’ve created. The group is just there to make some sort of logical sense of things. Here we take the files from our initial Lumberjack project:

lumberjackExample_png

and drag-and-drop them into our new darkskyClient project (note we created a group called Lumberjack to accept the new files into):

darkskyProject_png

making sure to Copy items into destinations group’s folder and Add to targets!

add_lumberjack_to_project_png

Now, create a new header file called Logging.h and add the following contents, or drag-n-drop Logging.h from the lumberjackExample project.

[objc]
#ifndef lumberjackExample_Logging_h
#define lumberjackExample_Logging_h

#import "DDLog.h"

#define ENTRY_LOG DDLogVerbose(@"%s ENTRY ", __PRETTY_FUNCTION__);
#define EXIT_LOG DDLogVerbose(@"%s EXIT ", __PRETTY_FUNCTION__);
#define ERROR_EXIT_LOG DDLogError(@"%s ERROR EXIT", __PRETTY_FUNCTION__);

#endif
[/objc]

Create a new header file called Constants.h, and then add your API key (obviously below you would have an actual key rather than useless dashes). We’ll also add a #define to the Interesting Storms API provided by DarkSky.

[objc]
#ifndef darkskyClient_Constants_h
#define darkskyClient_Constants_h

#define DARKSKY_API_KEY @"——————————–"

#define DARKSKY_INTERESTING_STORMS @"https://api.darkskyapp.com/v1/interesting"

#endif
[/objc]

We’re going to use the Interesting Storms API call to the DarkSky webservice, and just log the resulting JSON. Additional tutorials will take up where we will leave off, the intent of this tutorial is to highlight schemes and build configurations.

Go to your AppDelegate.m and add the following code after importing AppDelegate.h but before the @implementation:

[objc]
#import "Constants.h"
#import "Logging.h"
#import "DDTTYLogger.h"

#ifdef DEBUG
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
#else
static const int ddLogLevel = LOG_LEVEL_ERROR;
#endif
[/objc]

and then put the following as your application:didFinishLaunchingWithOptions: body:

[objc]
– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

[DDLog addLogger:[DDTTYLogger sharedInstance]];

ENTRY_LOG;

NSString* interestingStorms = [NSString stringWithFormat:@"%@/%@", DARKSKY_INTERESTING_STORMS, DARKSKY_API_KEY];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError* error;

NSString* interestingStormData = [NSString stringWithContentsOfURL:[NSURL URLWithString:interestingStorms]
encoding:NSUTF8StringEncoding
error:&error];
DDLogVerbose(@"API returned: %@", interestingStormData);
});

EXIT_LOG;
return YES;
}
[/objc]

If you have never worked with blocks before (the ^{ } business inside the dispatch_async call), don’t worry, this tutorial doesn’t require you to understand them (though you will need to understand eventually).

If you now run the application you should see something like:

in your output log.

As a brief aside, notice that the ENTRY and EXIT logs were both displayed well before the API returned call, even the the code shows the DDLogVerbose statement in the middle. The secret here is in the dispatch_async function, which if you are itching to read about, visit Apple’s Grand Central Dispatch reference for now.

Now, we have an application worthy of adding some schemes and build configurations to! Let’s say we are developing our Dark Sky client application and we want to build a version that doesn’t contain any debug logging in it. How are our logs even currently displayed in the first place? Recall that at the top of our AppDelegate.m we have:

[objc]
#ifdef DEBUG
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
#else
static const int ddLogLevel = LOG_LEVEL_ERROR;
#endif
[/objc]

So, somewhere DEBUG is being defined, but where? It could be defined somewhere in a header file, but in this case it comes included as a part of the Debug build configuration that XCode generated when you created the project. Go to your darkskyClient target and under Build Settings search for DEBUG. Notice under Apple LLVM compiler 4.2 – Preprocessing there’s an entry called Debug and next to it DEBUG=1.

debug_flag_png

This is how DEBUG is being defined. But how did XCode decide to use the Debug build configuration when you ran your client? That’s determined by the scheme you are currently using in XCode.

scheme_png

If you click on the darkskyClient text in the scheme menu, you’ll probably find that it is the only scheme available. But before we add additional schemes, let’s look at the default scheme. Click on the scheme menu again (the scheme menu is the left side of the bar above ‘Scheme’, whereas the right side of the bar is used to select which device or simulator you want to run the application on) and select Edit Scheme. You should see a dialog box like this:

darksky_scheme_png

Now, it might start to make a little sense! Notice the list of actions can perform: Build, Run, Test, Profile, Analyze, Archive. And notice that under each of these tags is a build configuration: Debug or Release. Here’s what this is saying: if you are using the darkskyClient scheme, and you select the Run action, it is going to use the Debug build configuration. Period. Now, of course you can change the build configuration for the Run action. But why would you want to do that when you can manage everything easily through schemes?

What we would like is two initial schemes: one for building a debug client and one for build a non-debug client. While one can argue the term for “non-debug” should not be simply “adhoc” it turns out that we release adhoc builds to our test community with debugging explicitly turned off. So in this example you are going to see two initial build configurations:

  • adhoc
  • debug

We are also going to name the schemes “production” to differentiate from the fact that we may have two additional types of builds: adhoc development and debug development.

Let’s change the default scheme first (darskyClient) and turn it into a DebugProduction scheme. Go to scheme menu and select Manage Schemes… and click on the darkskyClient name and it should highlight so you can edit it. Change it to DebugProduction.

name_debugproduction_png

Click OK and now notice that your scheme menu has changed:

debugproduction_scheme_png

Currently our DebugProduction scheme needs no further changes, but now let’s create our AdhocProduction scheme. Go back to the scheme menu and select Manage Schemes, but this time go down to the gear wheel and select Duplicate Scheme. Name the new scheme AdhocProduction and change the Build Configuration of the Run action from Release to Debug. Click OK and then OK again, and now notice you have two schemes: DebugProduction and AdhocProduction.

Select the AdhocProduction scheme and run your application. You should notice that you no get any log output! This is exactly what should expect from the way we configured our logging:

[objc]
#ifdef DEBUG
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
#else
static const int ddLogLevel = LOG_LEVEL_ERROR;
#endif
[/objc]

as well as the way the Release build configuration is set, recall that it does not define DEBUG. Of course, let’s say in the AdhocProduction build you wanted to see at least Info logs. Simply replace the above with:

[objc]
#ifdef DEBUG
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
#else
static const int ddLogLevel = LOG_LEVEL_INFO;
#endif
[/objc]

and then rerun your AdhocProduction build. You should still not see the ENTRY and EXIT logs, but you will see the interesting storm data JSON (assuming there are interesting storms somewhere!)

Before we leave this topic and blog post altogether, let’s create another scheme called DebugDevelopment. The purpose of the DebugDevelopment scheme is to build our client against, say, a development set of resources. Let’s say for arguments sake that the DarkSky service include development servers that it allowed webclients to test against. Rather than hit https://api.darkskyapp.com/v1/interesting you should hit https://dev-api.darkskyapp.com/v1/interesting. Let’s update our Constants.h to:

[objc]
#ifdef DEVELOPMENT_ENVIRONMENT
#define DARKSKY_INTERESTING_STORMS @"https://dev-api.darkskyapp.com/v1/interesting"
#else
#define DARKSKY_INTERESTING_STORMS @"https://api.darkskyapp.com/v1/interesting"
#endif
[/objc]

Now, we want to create a DebugDevelopment scheme that makes use of the development environment. In this case, go back to Manage Schemes and duplicate DebugProduction and call it DebugDevelopment. Uh-oh. We have a problem! There is only a Debug build configuration, so how do we distinguish between the two? Easy. Create a new build configuration. This feature is a little bit more hidden then the rest. Select on your project file, and then in the project viewer, select on the project file. A picture is worth a thousand words in this case.

add_build_config_png

Once you find the build configurations (there will be one called Debug and one called Release), select the plus button for configurations and duplicate the Debug configuration and name it DebugDevelopment. Now, we need to set our #define that we put in Constants.h. Go over to Build Settings tab of the project view and scroll down to the Apple LLVM compiler 4.2 – Preprocessing section, edit the DebugDevelopment defines and put DEVELOPMENT_ENVIRONMENT=1.

debugdevelopment_png

Now, that we have DebugDevelopment build configuration, go back and update your DebugDevelopment scheme to use the DebugDevelopment build configuration when running.

You might want to add something like:

[objc]
DDLogVerbose(@"Using DarkSky API Endpoint: %@", interestingStorms);
[/objc]

prior to the dispatch_async routine (which will dispatch the routine to call the webservice). Doing so and building the DebugDevelopment scheme you should see the following log output:

Switch over to the DebugProduction scheme and you will see:

And there you have it, a simple and effective technique for managing debug vs. adhoc builds, production vs. development environments. Note that there are other mechanisms by which one can toggle back and forth between production and development environments, perhaps include a toggle switch in your app, but then, you would want some mechanism available to compile that switch out. We’ve found through our development that maintaining separate schemes for the environments served our purposes well.

A complete example project is available in Github. Remember to obtain a DarkSky API key or you will be greeted with

when running the project!

Leave a Reply

Your email address will not be published. Required fields are marked *