iOS/Swift Series: [#08] Setup iOS environments

It’s best practice to have separate environments for your iOS apps, especially if they are communicating with any servers. For instance, consider an iOS app and three different available web backend environments: development, staging, and production.
What’s the best way to switch in the iOS app to use these different servers? What about all the other potential key/value pairs of information, like API keys — and what if some of that information needs to be secure and not checked into version control?
The simplest way is to create a file to store these environment variables as a string or other constants in the native language, like Swift
. However, this may lead to numerous issues - whether it be for security reasons or overall bad practices involving hardcoding flag values or relying on macros. Instead, let’s use separate files that we can avoid checking into version control that explicitly outlines the properties of the app that will change on an environmental basis.
Project Setup
Open Xcode and create a new project based on the Single View Application template.

Name your project and 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.

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).

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. Example:
- Debug (Development)
- Debug (Staging)
- Debug (Production)
- Release (Development)
- Release (Staging)
- Release (Production)
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.

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.

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).

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).

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.
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 UIKitstruct 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.
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.
