Unleashing your build settings
For years I've used Preprocessor Macros
in Build Settings
as a convenient place to store configuration specific details - this allowed different schemes to have different settings without having to make any code changes. I often applied this to the base URL for accessing the API or ensuring that I was sending analytical events to the correct Mixpanel project. This resulted in simple code, such as:
[WBSConfig sharedInstance].APIHost = BASE_API_URL;
rather than
#ifdef RELEASE
[WBSConfig sharedInstance].APIHost = "https://platform.example.com";
#else
[WBSConfig sharedInstance].APIHost = "https://staging.platform.example.com";
#endif
or even worse, having a developer manually changing the string 😱 (yes, I've actually seen this in production on a banking app).
The end of youthfulness
Sadly the joyfulness of our youth came to an end because unbeknown to us Apple had macros in its crosshairs and when Swift came along support for macros wasn't part of it 😫. However after digging about in Build Settings
I discovered new settings that are similar to Preprocessor Macros
:
Swift Compiler - Custom Flags
With Swift Compiler - Custom Flags
we can add our own flags and these flags act as lightweight Preprocessor Macros
. These custom flags are treated as booleans either they exist or not - assigning a value to them will have no effect as it's not possible to retrieve that value (in fact adding a value will result in the compiler treating that flag as if it doesn't exist). When declaring a custom flag we need to prefix with it -D
so if we want to add a release flag it would be -DRELEASE
and then to use it we remove the -D
so RELEASE
.
Ok, so we can now detect if a flag exists based on the configuration of our scheme however we can't assign a value to it. We need to combine these flags with some conditional programming to get the same result as we had before (similar to a macro approach we looked at, and discarded, above).
class AppEnvironment: NSObject {
// MARK: Networking
class func clientID() -> String {
var clientID: String?
#if DEBUG
clientID = "a5b0fb978fad9588af608c06382d45ee5396a29eb12f8b6bbec260569aebe45c"
#elseif RELEASE
clientID = "8ca5b0fb978fad06382d49e5396a29eb588af605e12f8b6bbec260569aebecc7"
#endif
assert(clientID != nil, "Client ID not set")
return clientID!
}
class func clientSecret() -> String {
var clientSecret: String?
#if DEBUG
clientSecret = "cd0cd93fe55c51007a45782de93c48c157de5f7f907267593309eea7d4c9064c"
#elseif RELEASE
clientSecret = "64cde93c48c157d2759330c51007a4578c90e5f7f99eea7d4cd0cd93fe550726"
#endif
assert(clientSecret != nil, "Client secret not set")
return clientSecret!
}
class func baseAPIURL() -> String {
var baseAPIURL: String?
#if DEBUG
baseAPIURL = "https://development.platform.example.com"
#elseif RELEASE
baseAPIURL = "https://platform.example.com"
#endif
assert(baseAPIURL != nil, "Base API URL not set")
return baseAPIURL!
}
// MARK: Analytics
class func mixpanelAppToken() -> String {
var mixpanelAppToken: String?
#if DEBUG
mixpanelAppToken = "a1278c97bb9d0e1034032011ca4a547c"
#elseif RELEASE
mixpanelAppToken = "32011ca4a547c7bba1278c903409d0e1"
#endif
assert(mixpanelAppToken != nil, "Mixpanel app token not set")
return mixpanelAppToken!
}
class func crashlyticsAPIKey() -> String {
var crashlyticsAPIKey: String?
#if DEBUG
crashlyticsAPIKey = "2e29b9629b2ff220ec706d264cafcf42fcd05abb"
#elseif RELEASE
crashlyticsAPIKey = "cf42d05ab2fd264cafb220ec706fc9b2e29b962f"
#endif
assert(crashlyticsAPIKey != nil, "Crashlytics API key not set")
return crashlyticsAPIKey!
}
}
In the above code snippet, we have encapsulated access to the flags behind a Swift interface so the rest of the app just calls a class method on the AppEnvironment
class and gets a string back without having to pass in any state.
It's not as clean a solution as Preprocessor Macros
however it still prevents the need to manually edit source files to change environment settings and actually helps to make these values a little less magic in nature (I think we are all agreed we should avoid magic strings and numbers if at all possible).
You can find the completed project by heading over to https://github.com/wibosco/UnleashingBuildSettings-Example