When you develop a module consisting of multiple libraries and programs such as SDK, you can define the common macros referenced by multiple projects.
For example, when you develop the SDK for communicating with a specific device, suppose the constants are changed for each target device. Then, you could switch the target device with the common macros in the header file.
In this case, you should define the macro if the project’s source files include the common header files. For example, create the Configuration.h
file. But how about a case with multiple projects where it is hard to put the common header file? For example, Swift doesn’t use the header file.
You can use the build configuration settings file in this case. If you want to try it on your machine while reading this article, you can download the sample project from the following link.
Create the configuration settings file
See the following article for information about creating and configuring the configuration settings file.
For example, we have created three projects with the following directory structure.
Modules
├── Common.xcconfig
├── Module1
│ ├── Module1
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── main.swift
│ └── Module1.xcodeproj
├── Module2
│ ├── Module2
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── main.m
│ └── Module2.xcodeproj
└── Module3
├── Module3
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── main.cpp
└── Module3.xcodeproj
Module1
, Module2
, and Module3
are programs built by separate project files and do not reference each other. Each project has two build configuration files, Debug.xcconfig
and Release.xcconfig
. Debug.xcconfig
and Release.xcconfing
are written as follows and include Modules/Common.xcconfig
.
#include "../Common.xcconfig"
By this, you can change the build configuration settings of Module1.xcodeproj
, Modue2.xcodeproj
, and Module3.xcodeproj
by editing Modules/Common.xcconfig
.
Code in Swift
Swift can define symbols but not symbols’ value. To define with Xcode, write in the form -DSYMBOL
at Other Swift Flags
of Swift Compiler - Custom Flags
in the build settings. Spaces separate multiple symbols.
Define in the configuration settings file
You can configure the Other Swift Flags
in the configuration settings file by OTHER_SWIFT_FLAGS
. For example, write the following code in the configuration settings file. In that case, the USE_PHYSICAL_DEVICE
symbol is defined the same as in the above screenshot.
OTHER_SWIFT_FLAGS = $(inherited) -DUSE_PHYSICAL_DEVICE
The configuration settings files are also reflected in what is displayed in Xcode.
Branch by the symbol in the code
To branch by the symbol in the Swift code, you can use #if
statement.
#if symbol
The code will be executed when the symbol is defined.
#else
The code will be executed when the symbol is not defined.
#endif
The primary difference between the #if
and the if
statements lies in whether the binary includes the code. Code branched by the #if
statement but not executed will not be compiled into the binary. On the other hand, code branched by the if
statement will be compiled, even if it is not executed. Thus, this non-executed code still exists within the binary.
The following code checks whether the USE_PHYSICAL_DEVICE
symbol is defined.
import Foundation
#if USE_PHYSICAL_DEVICE
print("Module1 DeviceType: Physical Device")
#else
print("Module1 DeviceType: Simulator")
#endif
Code in Objective-C
The symbols used in Objective-C are the same as C/C++. You can define them in the Xcode by writing in the form -DMacro
at Other C Flags
of Apple Clang - Custom Compiler Flags
in the build settings. Also, writing in the form -DMacro=Value
assigns the Value
to Macro
. Spaces separate multiple macros.
Define in the configuration settings file
You can configure the Other C Flags
in the configuration settings file by OTHER_CFLAGS
. For example, writing as follows defines USE_PHYSICAL_DEVICE
as the above screenshot.
OTHER_CFLAGS = $(inherited) -DUSE_PHYSICAL_DEVICE
The configuration settings file will also be reflected in the Xcode build settings screen.
Branches in the code
#ifdef
or #if defined
can branch the code with macros in Objective-C code.
#ifdef Macro
The code will be executed when the macro is defined.
#else
The code will be executed when the macro is not defined.
#endif
The following code branches by whether the USE_PHYSICAL_DEVICE
is defined.
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
#ifdef USE_PHYSICAL_DEVICE
NSLog(@"Module2 DeviceType: Physical Device");
#else
NSLog(@"Module2 DeviceType: Simulator");
#endif
}
return 0;
}
Code in C/C++
The code in C/C++ as the same as in the Objective-C.
Branches in the code
#ifdef
and #if defined
branches with macro in C/C++ too. The following code branches with whether the USE_PHYSICAL_DEVICE
is defined.
#include <iostream>
int main(int argc, const char * argv[]) {
#ifdef USE_PHYSICAL_DEVICE
std::cout << "Module3 DeviceType: Physical Device" << std::endl;
#else
std::cout << "Module3 DeviceType: Simulator" << std::endl;
#endif
return 0;
}
Execution Result
Run the above sample codes with an empty Common.xcconfig
file then the execution results to be the following.
Module1 DeviceType: Simulator
2022-07-19 16:03:43.534012+0900 Module2[3808:159692] Module2 DeviceType: Simulator
Module3 DeviceType: Simulator
Write the following code into Common.xcconfig
.
OTHER_SWIFT_FLAGS = $(inherited) -DUSE_PHYSICAL_DEVICE
OTHER_CFLAGS = $(inherited) -DUSE_PHYSICAL_DEVICE
Then run sample code and the program outputs as follows, and you can see that macro in a file are used by multiple projects.
Module1 DeviceType: Physical Device
2022-07-19 16:04:36.900064+0900 Module2[3843:162776] Module2 DeviceType: Physical Device
Module3 DeviceType: Physical Device