Managing Configuration-Dependent Variables in iOS Projects
A step-by-step guide for Xcode setup
Almost every app nowadays has to connect to some kind of back-end service and, usually, those services are deployed in multiple environments.
A classic example would be the dev/staging/production environments. The development environment is used for daily deploys and for devs to test their code.
The staging environment is used for stable releases for testers and clients to test. We all know what the production environment is for.
In this article, I will describe a step-by-step guide of what I think is the most straightforward way to handle multiple environments in an iOS app.
Here is a high-level overview of the setup:
- Add a project-level configuration for each environment.
- Modify the
Info.plistfile to contain a field that will resolve to the active configuration name at runtime.
- Use the active configuration from the
Info.plistfile to read properties from a configuration file at runtime
1. Add Project-Level Configuration for Each Environment
First, you will need to add all the environments as project-level configurations. You can do this by clicking on the Project -> Info tab in Xcode (see the below screenshot).
You can now select which configuration should be used at runtime by choosing the configuration in the Build Scheme -> Build Configuration setting.
In the below example the Debug configuration is selected when the build is run.
2. Modify the Info.plist File
OK, we are doing great so far but we still cannot do anything useful.
For one, we don’t know in which configuration the app is currently running. To help with that, we will modify the
Info.plist file to include the current active configuration name.
$(Configuration) variable is a special environment variable that Xcode will resolve to the current configuration name when compiling the project.
That means that if the project is compiled in the Debug configuration, the value under the Configuration property will be Debug. The
Info.plist file is a good place to store this information as we can easily retrieve it at runtime:
let activeConfigurationName = Bundle.main.object(forInfoDictionaryKey: “Configuration”) as? String
The above code will fetch the active configuration name from the
Info.plist file so we can use it at runtime.
3. Read Properties From a Configuration File Using the Current Configuration Name
We can now access the current configuration name at runtime, and we can use it to read variables from a configuration file.
Lets first define a
Configuration.plist file that will look like this:
Note that the configuration names have to be the same as the project configurations we defined in step 1.
We can now access the properties from this file at runtime using the active configuration name:
let activeConfigurationName = Bundle.main.object(forInfoDictionaryKey: “Configuration”) as! Stringlet configurationFilePath = Bundle.main.path(forResource: "Configuration", ofType: "plist")!let configurationDictionary = NSDictionary(contentsOfFile: configurationFilePath)!let activeConfigurationDictionary = configurationDictionary[activeConfigurationName] as! NSDictionarylet backendUrl = activeConfigurationDictionary["backendUrl"] as! String
And that's it, we can now retrieve the
backendUrl for the current active configuration!
In this guide, we have defined different configuration/environments and made additional set up to be able to fetch them at runtime.
As you can see, the process is not super easy but also not very hard. It is the setup that is usually recommended on blogs and SO. It works…
Unfortunately, it’s also not very safe since we are mostly using string APIs for fetching the properties and we have to make a lot of force unwraps or guard against nils.
When fetching the properties this way, a lot of things can go wrong:
- App will crash if we make a typo in the configuration filename.
- App will crash if the configuration file has an incorrect format.
- App will crash if we make a typo in the property name.
- App will crash if we cast the property to the wrong type.
- App will crash if we try to fetch a property that is not defined in the file.
All of this happens at runtime and the compiler cannot guard you against any errors. There has to be a better way right?
In the next article, I will introduce a tool that I wrote that will automate all of this setup and generate a Swift wrapper class that you can use to access the properties in a type-safe way using autocomplete. Stay tuned!