A build configuration defines the build settings that are used to build the product of a target. As the name suggests, build settings are used to build the product of a target. Even though you don't commonly access build settings in Swift, it can be useful to keep configuration and implementation separate. It is a pattern I have been using for several years. In this episode, I show you how to access build settings in Swift.

Creating the Project

Fire up Xcode and create a project by choosing New > Project... from Xcode's File menu. Choose the App template from the iOS > Application section. Give the project a name and choose a location for the project.

Creating the Project in Xcode

Adding User-Defined Build Settings in Xcode

Select the project in the Project Navigator on the left and click the Build Settings tab at the top.

Exploring Build Settings in Xcode

Scroll to the user-defined build settings at the bottom of the list of build settings. To add a user-defined build setting, click the + button at the top and choose Add User-Defined Setting from the list of options.

Adding a User-Defined Build Setting in Xcode

This adds a new build setting to the section of user-defined build settings. The naming convention for build settings is uppercased snake case, for example, API_BASE_URL. A project defines two build configurations by default, Debug and Release. We can set the value of API_BASE_URL for each build configuration. That is what makes build settings so useful.

Adding a User-Defined Build Setting in Xcode

Accessing Build Settings in Swift

It isn't possible to directly access build settings from the code you write. We need to store the values of the build settings we want to access in a location that is available at run time. The most obvious and common option is the bundle's Info.plist. Open the target's Info.plist. If you don't see the target's Info.plist in the Project Navigator, then you may need to make a temporary change to make it show up. As of Xcode 13, the target's Info.plist is generated at build time for some templates. This is easy to fix, though.

Select the target and click the General tab at the top. If you uncheck the Supports multiple windows checkbox in the Deployment Info section, the Info.plist file should show up. You can undo the change you made if the target does support multiple windows.

Open the target's Info.plist and add a new key-value pair. While not strictly necessary, we give the key the same name as the build setting. This avoid confusion if you are working in a team. Xcode sets the value at build time using the value of the build setting we defined earlier. We only need to tell Xcode which build setting it needs to read the value from. We do this by entering the name of the build setting, wrapping it in parentheses, and prefixing it with a dollar sign.

Accessing a Build Setting in Swift

The last step is reading the value that is stored in the bundle's Info.plist. Create a new Swift file with name Configuration.swift. You can name it whatever you like. Define an enum with name Configuration. We use the enum as a namespace. I write about this pattern in more detail in Swift Patterns: Namespaces.

import Foundation

enum Configuration {

}

We define a static, private method, string(for:), for accessing a key-value pair in the bundle's Info.plist. This is nothing more than a convenience method. The string(for:) method accepts the key of the key-value pair as an argument and returns the value of the key-value pair. In the body of the method, we ask the main bundle for the contents of the bundle's Info.plist, access the value for the given key, and forced cast it to a string.

import Foundation

enum Configuration {

    // MARK: - Helper Methods

    static private func string(for key: String) -> String {
        Bundle.main.infoDictionary?[key] as! String
    }

}

This is one of the few scenarios in which I use the exclamation mark. Why is that? Accessing a key-value pair you know exists should never fail, especially if the configuration of the application depends on the key-value pair to be present. In this example, we store the base URL of an API the application communicates with. What happens if the application isn't able to read the base URL from the bundle's Info.plist? That should never happen because it would break some of the application's functionality.

We don't stop here, though. There is a good reason why the string(for:) method is declared privately. We define a convenience property for accessing each of the user-defined build settings we want to access from code.

Define a static, variable property, apiBaseURL, of type URL. We access the value for the API_BASE_URL key by invoking the string(for:) method. We use the value to create and return a URL object. Notice that we forced unwrap the result of the initialization of the URL. We don't expect this to fail. If it does, then we have bigger problems to worry about.

import Foundation

enum Configuration {

    // MARK: - Public API

    static var apiBaseURL: URL {
        URL(string: string(for: "API_BASE_URL"))!
    }

    // MARK: - Helper Methods

    static private func string(for key: String) -> String {
        Bundle.main.infoDictionary?[key] as! String
    }

}

Navigate to ContentView.swift and replace Hello, world! with the base URL to check that everything is working as expected.

struct ContentView: View {
    var body: some View {
        Text(Configuration.apiBaseURL.absoluteString)
            .padding()
    }
}

Build and run the application in a simulator. The application should display the value of the API_BASE_URL build setting.

Accessing the Build Setting

Security

You need to be mindful what information you store in the bundle's Info.plist. Don't use it to store sensitive information. You need to make it as difficult as possible for people with less good intentions to access information they shouldn't have access to. That is why I usually only store the build configuration in the bundle's Info.plist. I explain that technique in detail in Switching Environments With Configurations.

Keep in mind that sensitive information that is embedded in your application is never safe. Period. If someone wants access to a piece of information that is embedded in your application, then they can if they try hard enough. By storing sensitive information in the bundle's Info.plist you make it that much easier to access that information.