Switching Environments With Configurations

Switching Environments With Configurations

Most mobile applications connect to one or more services in the cloud. The services you connect to can differ depending on the development phase the project is in. For example, you may be using a staging environment during development, a testing environment during beta testing, and a production environment for the App Store. Switching between environments can be cumbersome and prone to errors.

Over the years, I have experimented with several strategies. The simplest and quickest solution is using a macro to set the environment and choose a configuration or environment based on the value of the macro. Some developers use targets for creating builds for a particular configuration, but I don't like this approach since targets are the wrong solution to the problem.

The best solution I have come across are configurations, also known as build configurations. Configurations are defined at the project level, which means that you can use them for every target of your project and leverage schemes to define the configuration for a particular build.

In this tutorial, I show you how to set up a project that defines several configurations. In combination with schemes, configurations enable you to easily switch between environments.

Project Setup

Open Xcode and create a new project based on the Single View Application template.

Choosing the Single View Application Template

Name your project and set Language to Swift.

Set Language to Swift

Adding Configurations

Every Xcode project includes two default configurations, Debug and Release. For some projects, these configurations are sufficient. However, assume for a moment that you are building an application that interacts with a web service. The web service defines two environments, Staging and Production. Configurations can help you to quickly switch between these environments with very little effort.

With the project opened in Xcode, select the project in the Project Navigator on the left. Configurations are defined at the project level. To inspect the list of configurations, open the Info tab at the top and look for the Configurations section.

Inspecting the Project's Configurations

During development, it may be necessary to switch between a staging environment and a production environment. Let us start by creating a configuration for each environment. Double-click Debug and change the name of the configuration to Debug (Staging). Click the + button at the bottom of the table and create a new configuration by choosing Duplicate "Debug" Configuration from the menu that appears. Name the new configuration Debug (Production).

Adding a Configuration

If you also need the ability to switch environments for release builds, then repeat the above steps for the Release configuration. This can be useful if a client asks for a release build for every environment. If you are using an automated build process, then this is the way to go.

Storing the Configuration in Info.plist

To ensure that we have access to the configuration at runtime, we store it in the target's Info.plist. This is very easy to do. In the Project Navigator on the left, select Info.plist, right-click, and choose Add Row to create a new key-value pair. Set the key to Configuration and the value to $(CONFIGURATION).

The configuration that is used during the build process is accessible through an environment variable, CONFIGURATION. This makes it easy for us to dynamically update the target's Info.plist during the build process.

Add Configuration to Info.plist

Defining Schemes

With the current configuration stored in the target's Info.plist, it is time to add the ability to easily switch environments. We do this by adding a scheme for every configuration. At the top left, click the active scheme and choose Manage Schemes.

Manage Schemes

Select the scheme and click the gear icon at the bottom of the table. From the menu, select Duplicate. Repeat this step one more time. You should have three schemes in total. Double-click the new schemes and name them Configurations Debug (Staging) and Configurations Debug (Production).

Duplicating Schemes

If you work in a team or use an automated build process, then it is interesting to check the Shared checkbox on the right to share the schemes with your teammates.

Select Configurations Debug (Staging) and click Edit... at the bottom to edit the scheme. We need to tell the scheme which configuration to use when the application is run. Select Run on the left and make sure the Info tab is open at the top. Set Build Configuration to Debug (Staging). Do the same for the Debug (Production) scheme, setting Build Configuration to Debug (Production).

Setting the Build Configuration of the Scheme

Loading Configuration Details

There are several ways to expose or load the details associated with a particular configuration or environment. You can store them in a JSON file and include this file in the application bundle or you can store them in a property list for easy access. The downside is that other people can easily access the contents of your application bundle by downloading your application from the App Store. If the data is not critical, then that may be fine. If the environment details include sensitive data, such as tokens and API keys, then you need to look for a safer solution.

I would like to end this tutorial with an alternative solution. Add a new file to your Xcode project and name it Configuration.swift. We start by declaring an enumeration, Environment, that defines the possible environments for the application. For each environment variable, we declare a computed property. The value returned for each computed property depends on the current environment, Staging or Production.

enum Environment: String {
    case Staging = "staging"
    case Production = "production"

    var baseURL: String {
        switch self {
        case .Staging: return "https://staging-api.myservice.com"
        case .Production: return "https://api.myservice.com"
        }
    }

    var token: String {
        switch self {
        case .Staging: return "lktopir156dsq16sbi8"
        case .Production: return "5zdsegr16ipsbi1lktp"
        }
    }
}

We also define a structure, Configuration. The Configuration structure declares a lazy stored property, environment of type Environment. The implementation is straightforward. We load the configuration from the target's Info.plist. Remember that we dynamically update and store the configuration in the target's Info.plist. We then use a simple technique to parse the configuration name and initialize the corresponding Environment instance.

import UIKit

struct Configuration {
    lazy var environment: Environment = {
        if let configuration = NSBundle.mainBundle().objectForInfoDictionaryKey("Configuration") as? String {
            if configuration.rangeOfString("Staging") != nil {
                return Environment.Staging
            }
        }

        return Environment.Production
    }()
}

Don't forget to add an import statement for the UIKit (or Foundation) framework at the top to gain access to the NSBundle class.

Testing the Implementation

Open ViewController.swift and update viewDidLoad() as shown below.

override func viewDidLoad() {
    super.viewDidLoad()

    // Initialize Configuration
    var configuration = Configuration()

    print(configuration.environment.baseURL)
    print(configuration.environment.token)
}

Select the Debug (Staging) scheme at the top and run the application in the simulator or on a physical device. This is what the output should look like in Xcode.

https://staging-api.myservice.com
lktopir156dsq16sbi8

If you select the Debug (Production) or Release schemes, the production environment is automatically selected. It is a good practice to use the production environment as the default environment. This avoids possible configuration issues for production builds.

Creating Archives

In this tutorial, we only set the build configuration for running the application in the simulator or on a physical device. If you want to select a particular build configuration for archiving, you need to specify that in the scheme.

Click the active scheme at the top and choose Edit Scheme.... Select the Archive tab on the left and set Build Configuration to the desired configuration. The default is Release.

Configuring Schemes

What's Next?

There are other options to easily switch environments, but I prefer this one because it keeps everything confined to the Xcode project. For example, it is possible to inject environment variables at compile time, but that solution may introduce problems if build scripts are modified or a project changes ownership.

I am curious to hear what techniques you use to automate trivial tasks, such as switching environments. Questions? Leave them in the comments below or reach out to me on Twitter. You can download the source files of the tutorial from GitHub.