SDKなどの複数のライブラリやプログラムで構成されるモジュールを開発しているときなど、複数のプロジェクト間で共通のマクロ定義を参照することがあります。
たとえば、ある特定の機器と通信するSDKを開発しているときに、対象となる機器によって定数を切り替える必要があるとします。どの機器用にビルドするのかを、ヘッダファイルのマクロで切り替えられるようになっているとします。
このようなとき、共通のヘッダファイルをインクルードしているのであれば、そこで定義します。マクロを定義します。たとえば、Configuration.h
ファイルなどを用意するなどです。しかし、複数のプロジェクトファイルで構成され、そのような共通ファイルを置くことも難しいというケースではどうでしょうか?たとえば、Swiftであればヘッダファイルは使用しないですね。
このような場合にはビルド設定ファイルを使用します。記事を見ながらコードを実行してみたい方は、次のリンクからサンプルファイルをダウンロードしてください。
ビルド設定ファイルを作成する
ビルド設定ファイルの作成方法とプロジェクトへの設定方法については、以下の記事を参照してください。
ここでは、例として次のようなディレクトリ構成で3つのプロジェクトを作成しました。
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
、Module3
はそれぞれ別々のプロジェクトファイルによりビルドされるプログラムになっており、お互いは参照していません。各プロジェクトはDebug.xcconfig
、Release.xcconfig
というビルド設定ファイルを持っています。名前の通り、デバッグ用とリリース用です。Debug.xcconfig
とRelease.xcconfig
は次のように書かれており、Modules/Common.xcconfig
を参照するようになっています。
#include "../Common.xcconfig"
これにより、Modules/Common.xcconfig
ファイルを編集することで、Module1.xcodeproj
、Module2.xcodeproj
、Module3.xcodeproj
のビルド設定が変更できるようになります。
Swiftのコード
Swiftはシンボルの定義だけできるようになっています。Xcode上で定義するときは、ビルド設定のSwift Compiler - Custom Flags
のOther Swift Flags
に-Dシンボル名
の書式で定義します。複数あるときは、スペース区切りで列挙します。
ビルド設定ファイルで定義するには
Other Swift Flags
をビルド設定ファイルで定義するときは、OTHER_SWIFT_FLAGS
を使用します。たとえば、スクリーショットと同様に、USE_PHYSICAL_DEVICE
を定義するときは次のように記述します。
OTHER_SWIFT_FLAGS = $(inherited) -DUSE_PHYSICAL_DEVICE
ビルド設定ファイルに上記のように記述すると、スクリーンショットのようにXcodeで表示したビルド設定画面にも反映されます。
コードでの分岐方法
Swiftのコードで定義したシンボルによってコードを分岐させるには、#if
文を使用します。
#if シンボル
シンボルが定義されているときにビルドされるコード
#else
シンボルが定義されていないときにビルドされるコード
#endif
#if
文とif
文の大きな違いは、#if
で分岐されて実行されないコードは、バイナリに含まれないということです。つまりコンパイル対象になりません。一方、if
文の場合はどちらもコンパイルされバイナリに含まれて実行されないというだけです。
次のコードは、USE_PHYSICAL_DEVICE
が定義されているかどうかをチェックしています。
import Foundation
#if USE_PHYSICAL_DEVICE
print("Module1 DeviceType: Physical Device")
#else
print("Module1 DeviceType: Simulator")
#endif
Objective-Cのコード
Objective-CはC言語およびC++と共通の定義を参照します。Xcode上で定義するには、ビルド設定のApple Clang - Custom Compiler Flags
のOther C Flags
に-Dマクロ
の書式で定義します。-Dマクロ=値
で定義した場合は、マクロに値を割り当てます。複数あるときは、スペース区切りで列挙します。
ビルド設定ファイルで定義するには
Other C Flags
をビルド設定ファイルで定義するときは、OTHER_CFLAGS
を使用します。たとえば、スクリーンショットと同様に、USE_PHYSICAL_DEVICE
を定義するときは次のように記述します。
OTHER_CFLAGS = $(inherited) -DUSE_PHYSICAL_DEVICE
ビルド設定ファイルに上記のように記述すると、Xcodeのビルド設定画面にも反映されます。
コードでの分岐方法
コードもObjective-Cと同じです。#ifdef
や#if defined
を使用します。次のコードは、USE_PHYSICAL_DEVICE
が定義されているかによって切り替えているコード例です。
#ifdef マクロ
マクロが定義されているときにビルドされるコード
#else
マクロが定義されていないときにビルドされるコード
#endif
次のコードは、USE_PHYSICAL_DEVICE
が定義されているかどうかによって切り替えているコードです。
#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;
}
C言語/C++のコード
C言語とC++の場合は、Objective-Cと同じです。
コードでの分岐方法
コードもObjective-Cと同じです。#ifdef
や#if defined
を使用します。次のコードはUSE_PHYSICAL_DEVICE
が定義されているかどうかによって切り替えているコード例です。
#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;
}
実行結果
ここまでのサンプルコードをCommon.xcconfig
ファイルを空にして実行すると、次のように出力されます。
Module1 DeviceType: Simulator
2022-07-19 16:03:43.534012+0900 Module2[3808:159692] Module2 DeviceType: Simulator
Module3 DeviceType: Simulator
Common.xcconfig
に以下のように記述して、マクロを定義します。
OTHER_SWIFT_FLAGS = $(inherited) -DUSE_PHYSICAL_DEVICE
OTHER_CFLAGS = $(inherited) -DUSE_PHYSICAL_DEVICE
あらためてサンプルコードをビルドして実行すると、次のように出力され、複数のプロジェクトのマクロを1つのファイルで定義できていることが確認できます。
Module1 DeviceType: Physical Device
2022-07-19 16:04:36.900064+0900 Module2[3843:162776] Module2 DeviceType: Physical Device
Module3 DeviceType: Physical Device