Availability by Swift version
01 何が問題だったのか
Swift の @available(...) 属性は、宣言がいつから使えるか、いつ非推奨や廃止になったかを示す仕組みです。しかし Swift 3 以前は、その条件として指定できるのはプラットフォーム(iOS や macOS など)と OS バージョンだけで、Swift 言語のバージョン を条件に取ることはできませんでした。
Swift は 3.0 以降、-swift-version N コンパイラフラグによって、古い Swift バージョンのソース互換モードでビルドできるようになります。これに伴い、標準ライブラリの API も Swift のバージョンによって追加・改名・廃止されていきます。たとえば Swift 3.1 で削除される API があったとき、-swift-version 3.0 でビルドされる既存コードからは引き続き見えてほしい、という状況が発生します。
既存の仕組みでこれを実現しようとすると、#if swift(>= 3.1) のような静的な条件付きコンパイルに頼ることになります。ただしこれは宣言をコンパイル時に取り除いてしまう仕組みなので、標準ライブラリを対応したい Swift バージョンの数だけ別々にコンパイルしないといけません。
#if swift(>=3.1)
// Swift 3.1 以降用のビルド
#else
class Foo {
// ...
}
#endif
標準ライブラリのように広く配布されるライブラリにとって、サポートするバージョンごとにビルドを用意するのは現実的ではありません。ライブラリを 一度だけ ビルドしておき、利用側の -swift-version の指定に応じて見える API が切り替わる、という仕組みが望まれました。
02 どのように解決されるのか
@available(...) 属性に、プラットフォームと並んで swift という種類の条件を追加します。これにより、宣言が使える Swift バージョンの範囲を、従来のプラットフォームバージョンとまったく同じ感覚で指定できるようになります。
基本的な使い方
たとえば Swift 3.1 で廃止される API は、次のように書けます。
@available(swift, obsoleted: 3.1)
class Foo {
// ...
}
この宣言を含むライブラリは一度だけビルドしておけば、利用側のコードが -swift-version 3.0 でコンパイルされているときは Foo が見え、Swift 3.1 以降としてコンパイルされているときは見えなくなります。
introduced / deprecated / obsoleted といった引数も、プラットフォーム版と同じように使えます。
@available(swift, introduced: 3.1)
func newAPI() { /* ... */ }
@available(swift, deprecated: 4.0, obsoleted: 5.0)
func oldAPI() { /* ... */ }
省略形
プラットフォーム版に @available(iOS 10, *) という省略形があるのと同様に、swift でも introduced: を省いた書き方ができます。
// @available(swift, introduced: 3) と同じ
@available(swift 3)
func sinceSwift3() { /* ... */ }
プラットフォーム条件との組み合わせ
ひとつの宣言に swift 版と従来のプラットフォーム版の @available を両方付けることもできます。このとき両者は 論理積(AND) として扱われ、Swift バージョン条件と、デプロイ先のプラットフォーム条件の 両方を満たす ときだけ、その宣言が利用可能とみなされます。
@available(swift, introduced: 3.1)
@available(iOS, introduced: 10.0)
func requiresBoth() { /* ... */ }
一方で、プラットフォーム省略形のリストに swift を混ぜることは 許可されません。プラットフォーム省略形のリストは論理和(OR)として読まれるため、そこに論理積の要素が混じると読み手にとって意味が曖昧になるからです。次のような書き方はエラーになります。
// error: どちらも書けない
@available(swift 3, *)
@available(swift 3, iOS 10, *)
Swift 条件とプラットフォーム条件を組み合わせたいときは、前述のように @available 属性を 2 つに分けて書きます。