Adding a Build Configuration Import Test
01 何が問題だったのか
Swiftにはこの提案以前から、プラットフォームやアーキテクチャに応じてコードを切り替えるためのビルド設定がありました。具体的には次のようなものです。
os(...):OSX,iOS,watchOS,tvOS,Linux,Windows,FreeBSDなどの OS を判定arch(...):x86_64,arm,arm64,i386,powerpc64,powerpc64leなどの CPU アーキテクチャを判定swift(>=...): Swift 言語のバージョンを判定
しかし、「特定のモジュール(フレームワーク)が今のターゲットで利用できるかどうか」を直接判定する手段がありませんでした。
OS で代用するとプラットフォーム追加に弱い
モジュールの有無を判定したいときは、これまで OS 判定で代用するしかありませんでした。たとえば、UIKit が使えるかどうかを判定したい場合、次のように OS の組み合わせで書きます。
#if os(iOS) || os(tvOS) || os(watchOS)
// UIKit が使える前提のコード
#endif
このような包含的(inclusive)な OS 判定は、Swift が新しいプラットフォームに対応するたびに見直しが必要になります。逆に、Linux を除外することで Apple プラットフォーム全般を狙うような排他的(exclusive)な判定は、Windows や FreeBSD のような新しい OS ビルド設定が追加された時点で意図が崩れてしまいます。
// 排他的な OS 判定はもろい
#if !os(Linux)
// OSX, iOS, watchOS, tvOS のつもりでも
// Windows, FreeBSD まで含んでしまう
#endif
判定したいのは API の有無
実際に分岐させたいのは「この OS か」ではなく「この API(モジュール)が使えるか」であることがほとんどです。同じ UIKit でも iOS と tvOS の両方で使えますし、SpriteKit や Metal のように、将来的にシミュレータや別のプラットフォームへ対応範囲が広がる可能性のあるモジュールもあります。OS で間接的に判定していると、こうした対応範囲の変化に追従するためにコードの書き換えが必要になります。
また、サードパーティのフレームワークが特定のプラットフォームや OS バージョンでしか提供されないケースもあり、Swift だけでは「存在すれば使い、なければフォールバックする」という切り替えを書く手段がなく、Objective-C 側で動的にロードするといった回避策が取られていました。
結局のところ、問題は「モジュールの利用可否」を直接問い合わせる方法が Swift のビルド設定に用意されていなかったことです。
02 どのように解決されるのか
新しいビルド設定 canImport(...) を追加し、指定したモジュールが現在のビルド環境でインポート可能かどうかをコンパイル時に判定できるようにします。C/Clang の __has_include に相当する仕組みを、Swift のモジュール単位で提供するものです。
基本的な使い方
#if canImport(モジュール名) の形で記述します。コンパイル時にそのモジュールがリンク可能であれば、分岐が有効になります。
#if canImport(UIKit)
import UIKit
// UIKit を使うコード
#elseif canImport(Cocoa)
import Cocoa
// macOS 向けのコード
#else
// どちらも使えない環境向けのフォールバック
#endif
canImport 自体は モジュールをインポートしません。あくまで「インポートできるかどうか」を調べるだけで、実際に使うには従来どおり import が必要です。これにより、判定の分岐と、実際にそのモジュールの API を使う分岐を別々に書くことができます。
#if canImport(Metal)
// Metal API を使った解法を提供
#else
// Metal が使えない環境向けの代替解法
#endif
OS 判定とのちがい
canImport はプラットフォームの判定を目的とした仕組みではなく、あくまで API の利用可否 を判定するためのものです。そのため、次のような利点があります。
- 条件が API の可用性そのものに紐づくので、対応プラットフォームが増減してもコードの意図が崩れにくい
os(iOS) || os(tvOS) || os(watchOS)のような長い条件式を書かずに済み、読みやすく誤りにくい- ファースト/サードパーティを問わず、任意のモジュール名を指定できる
モジュール名は固定のリストに縛られておらず、任意の識別子を渡せます。コンパイラはコンパイル時にそのモジュールが当該ターゲットでリンクできるかどうかを調べ、結果に従ってビルド対象のコードを決めます。
使いどころ
canImport は、同じソースを複数プラットフォーム向けに共有したいときに特に役立ちます。たとえば、Apple プラットフォームでは Foundation・UIKit・Cocoa などのフレームワークを使い、Linux ではそれらが使えないためフォールバックを用意する、といったケースです。OS 名を列挙する代わりに、使いたいフレームワーク自体の有無で分岐できるため、Swift が新しいプラットフォームに対応したときにも条件式をそのまま流用しやすくなります。