Software program improvement finest practices
prescribe
strict separation of configuration from code.
But builders on Apple platforms
typically battle to sq. these pointers with Xcode’s project-heavy workflow.
Understanding what every venture setting does
and the way all of them work together with each other
is a talent that may take years to hone.
And the truth that a lot of this data
is buried deep throughout the GUIs of Xcode does us no favors.
Navigate to the “Construct Settings” tab of the venture editor,
and also you’ll be greeted by tons of of construct settings
unfold throughout layers of tasks, targets, and configurations —
and that’s to say nothing of the opposite six tabs!

Fortuitously,
there’s a greater option to handle all of this configuration
that doesn’t contain clicking by a maze of tabs and disclosure arrows.
This week,
we’ll present you the way you should utilize text-based xcconfig
recordsdata
to externalize construct settings from Xcode
to make your tasks extra compact, understandable, and highly effective.
Xcode construct configuration recordsdata,
extra generally recognized by their xcconfig
file extension,
permit construct settings in your app to be declared and managed with out Xcode.
They’re plain textual content,
which suggests they’re a lot friendlier to supply management techniques
and may be modified with any editor.
Essentially,
every configuration file consists of a sequence of key-value assignments
with the next syntax:
BUILD_SETTING_NAME = worth
For instance,
to specify the Swift language model for a venture,
you’d specify the SWIFT_VERSION
construct setting like so:
SWIFT_VERSION = 5.0
At first look,
xcconfig
recordsdata bear a placing resemblance to .env
recordsdata,
with their easy, newline-delimited syntax.
However there’s extra to Xcode construct configuration recordsdata than meets the attention.
Behold!
Retaining Present Values
To append relatively than change current definitions,
use the $(inherited)
variable like so:
BUILD_SETTING_NAME = $(inherited)extra worth
You usually do that to construct up lists of values,
such because the paths through which
the compiler searches for frameworks
to seek out included header recordsdata
(FRAMEWORK_SEARCH_PATHS
):
FRAMEWORK_SEARCH_PATHS = $(inherited) $(PROJECT_DIR)
Xcode assigns inherited values within the following order
(from lowest to highest priority):
- Platform Defaults
- Xcode Undertaking xcconfig File
- Xcode Undertaking File Construct Settings
- Goal xcconfig File
- Goal Construct Settings
Referencing Values
You possibly can substitute values from different settings
by their declaration identify
with the next syntax:
BUILD_SETTING_NAME = $(ANOTHER_BUILD_SETTING_NAME)
Substitutions can be utilized to
outline new variables in keeping with current values,
or inline to construct up new values dynamically.
OBJROOT = $(SYMROOT)
CONFIGURATION_BUILD_DIR = $(BUILD_DIR)/$(CONFIGURATION)-$(PLATFORM_NAME)
Setting Fallback Values for Referenced Construct Settings
In Xcode 11.4 and later,
you should utilize the default
analysis operator
to specify a fallback worth to make use of
if the referenced construct setting evaluates as empty.
$(BUILD_SETTING_NAME:default=worth)
Conditionalizing Construct Settings
You possibly can conditionalize construct settings in keeping with their
SDK (sdk
), structure (arch
), and / or configuration (config
)
in keeping with the next syntax:
BUILD_SETTING_NAME[sdk=sdk] = worth for specified sdk
BUILD_SETTING_NAME[arch=architecture] = worth for specified structure
BUILD_SETTING_NAME[config=configuration] = worth for specified configuration
Given a alternative between a number of definitions of the identical construct setting,
the compiler resolves in keeping with specificity.
BUILD_SETTING_NAME[sdk=sdk][arch=architecture] = worth for specified sdk and architectures
BUILD_SETTING_NAME[sdk=*][arch=architecture] = worth for all different sdks with specified structure
For instance,
you would possibly specify the next construct setting
to hurry up native builds by solely compiling for the lively structure:
ONLY_ACTIVE_ARCH[config=Debug][sdk=*][arch=*] = YES
Together with Construct Settings from Different Configuration Information
A construct configuration file can embody settings from different configuration recordsdata
utilizing the identical #embody
syntax
because the equal C
directive
on which this performance relies:
#embody "path/to/File.xcconfig"
As we’ll see in a while within the article,
you’ll be able to benefit from this to construct up cascading lists of construct settings
in actually highly effective methods.
Creating Construct Configuration Information
To create a construct configuration file,
choose the “File > New File…” menu merchandise (⌘N),
scroll all the way down to the part labeled “Different”,
and choose the Configuration Settings File template.
Subsequent, put it aside someplace in your venture listing,
ensuring so as to add it to your required targets

When you’ve created an xcconfig
file,
you’ll be able to assign it to a number of construct configurations
for its related targets.

Now that we’ve lined the fundamentals of utilizing Xcode construct configuration recordsdata
let’s have a look at a few examples of how you should utilize them
to handle improvement, stage, and manufacturing environments.
Customizing App Identify and Icon for Inside Builds
Creating an iOS app normally includes
juggling varied inside builds
in your simulators and check gadgets
(in addition to the newest model from the App Retailer,
to make use of as a reference).
You can also make issues simpler on your self
with xcconfig
recordsdata that assign every configuration
a definite identify and app icon.
// Improvement.xcconfig
PRODUCT_NAME = $(inherited) α
ASSETCATALOG_COMPILER_APPICON_NAME = App Icon-Alpha
//////////////////////////////////////////////////
// Staging.xcconfig
PRODUCT_NAME = $(inherited) β
ASSETCATALOG_COMPILER_APPICON_NAME = App Icon-Beta
Managing Constants Throughout Completely different Environments
In case your backend builders comport themselves in keeping with the aforementioned
12 Issue App philosophy,
then they’ll have separate endpoints for
improvement, stage, and manufacturing environments.
On iOS,
maybe the most typical method to managing these environments
is to make use of conditional compilation statements
with construct settings like DEBUG
.
import Basis
#if DEBUG
let api Base URL = URL(string: "https://api.staging.instance.com")!
#else
let api Base URL = URL(string: "https://api.instance.com")!
#endif
This will get the job finished,
however runs afoul of the canon of code / configuration separation.
An alternate method takes these environment-specific values
and places them the place they belong —
into xcconfig
recordsdata.
// Improvement.xcconfig
API_BASE_URL = api.staging.instance.com
//////////////////////////////////////////
// Manufacturing.xcconfig
API_BASE_URL = api.instance.com
Nonetheless,
to tug these values programmatically,
we’ll have to take one extra step:
Accessing Construct Settings from Swift
Construct settings outlined by
the Xcode venture file, xcconfig
recordsdata, and setting variables,
are solely obtainable at construct time.
Once you run the compiled app,
none of that surrounding context is obtainable.
(And thank goodness for that!)
However wait a sec —
don’t you keep in mind seeing a few of these construct settings earlier than
in a kind of different tabs?
Data, was it?
Because it so occurs,
that information tab is definitely only a fancy presentation of
the goal’s Data.plist
file.
At construct time,
that Data.plist
file is compiled
in keeping with the construct settings supplied
and copied into the ensuing app bundle.
Due to this fact,
by including references to $(API_BASE_URL)
,
you’ll be able to entry the values for these settings
by the information
property of Basis’s Bundle
API.
Neat!

Following this method,
we would do one thing like the next:
import Basis
enum Configuration {
enum Error: Swift.Error {
case lacking Key, invalid Worth
}
static func worth<T>(for key: String) throws -> T the place T: Lossless String Convertible {
guard let object = Bundle.essential.object(for Data Dictionary Key:key) else {
throw Error.lacking Key
}
change object {
case let worth as T:
return worth
case let string as String:
guard let worth = T(string) else { fallthrough }
return worth
default:
throw Error.invalid Worth
}
}
}
enum API {
static var base URL: URL {
return strive! URL(string: "https://" + Configuration.worth(for: "API_BASE_URL"))!
}
}
When seen from the decision website,
we discover that this method harmonizes superbly
with our greatest practices —
not a single hard-coded fixed in sight!
let url = URL(string: path, relative To: API.base URL)!
var request = URLRequest(url: url)
request.http Methodology = methodology
Xcode tasks are monolithic, fragile, and opaque.
They’re a supply of friction for collaboration amongst crew members
and usually a drag to work with.
Fortuitously,
xcconfig
recordsdata go a protracted option to deal with these ache factors.
Shifting configuration out of Xcode and into xcconfig
recordsdata
confers a mess of advantages
and affords a option to distance your venture from the particulars of Xcode
with out leaving the Cupertino-approved “comfortable path”.