Xcode 15.3 を使って今のうちに Swift 6 へのアップデートに備える

2024.03.08

Xcode 15.3ではSwift 5.10に対応している。Swift 5.10の次はSwift 6.0と言われており、順当にいけばXcode 16.0で採用されるだろう。Swift 6.0ではいくつかの機能がデフォルトで有効となり、これまでのSwiftの書き方ではエラーになってしまい、開発の現場では混乱が予想される。

例年通りであればXcodeのメジャーアップデートは9月で、一昨日Xcode 15.3がリリースされたばかりなので気が早いとは思うが、Xcode 16.0へのアップデート時にスムーズに移行できるように、また問題が発生しないように早めに対応しておきたい。

Swift 5.8以降、今後デフォルトで有効になる機能を「先行的に有効する機能」が存在している。Xcode 15.3でも利用が可能なので、この機能を使ってあらかじめSwift 6の機能に先行して対応しておくとよいだろう。

例のごとく、個人開発している「Four Cropper」を例にして、Xcode 15.3で「将来有効になる機能」を有効にして対応をおこなった。

Xcode 15.3 で 将来のSwiftの機能を利用するフラグを有効にする

Xcode 15.3 で Swift 6 や 7 など、将来のバージョンのSwiftでデフォルト有効になる機能を利用する方法を紹介する。

Xcode でBuild Settings -> Swift Compiler -> Other Swift Flag に対して、以下のように「-enable-upcoming-feature 機能名」を追加する。下図では -enable-upcoming-feature ForwardTrailingClosures を追加している。

機能名については、以下のリストを参考にしてほしい。ここでは treastrain氏がリストアップした機能のうち、私のアプリで影響のあったものを掲載している。

各プロポーザルについては「swift-evolution/proposals at main · apple/swift-evolution」のドキュメントを確認して欲しい。たとえば、SE-0335 ExistentialAnyであれば、0335で検索すればよい。

これらは、提案順に並んでおり、「既にSwift 5.x で対応済みの機能」と「まだデフォルト有効になっていない機能」「まだ組み込まれていない機能」などが混在しており、どれが Swift 6 で有効になる機能なのかわからないため、私は有識者の記事を参考にしている。

手間を省きたい場合は直接 project.pbxproj を編集する

XcodeのGUIを使用してフラグと機能名をひとつずつ入力するのは非常に手間がかかる。特に複数のアプリをメンテナンスしている場合には入力ミスをしてしまうリスクも考えられる。

エラーが大量に発生してしまうので、すべての機能を一度に有効にすることはおすすめできないが、/PATH/PROJECT_NAME.xcodeproj/project.pbxprojを直接エディタで開いて、以下のように変更すると手間が省けて良いだろう。

OTHER_SWIFT_FLAGS = "$(inherited) -enable-upcoming-feature ForwardTrailingClosures -enable-upcoming-feature BareSlashRegexLiterals -enable-upcoming-feature ConciseMagicFile -enable-upcoming-feature ImportObjcForwardDeclarations -enable-upcoming-feature DisableOutwardActorInference -enable-upcoming-feature DeprecateApplicationMain -enable-upcoming-feature IsolatedDefaultValues -enable-upcoming-feature GlobalConcurrency -enable-upcoming-feature ExistentialAny";

変更を保存したあと、Xcodeを開いて、Other Swift Flagsの値が正しく入力されていることを確認する。

入力に失敗している場合は、Xcodeで該当のプロジェクトが開けなくなる可能性があるので注意が必要だ。

実際に試してみた

前述の通り、いくつかの機能を有効にしたところ、エラーとワーニングが発生した。

ExistentialAny を有効にするとエラーになった

Four Cropperは単機能でコード量のアプリだが、「-enable-upcoming-feature ExistentialAny」を追加するとエラーが多発した。下図に表示されているのは発生したエラーのうちの一部。

どのようなコードでエラーが発生したかというと以下のようなものだ。FirebaseProviderProtocol プロトコルがあって、FirebaseProviderMockFirebaseProvider に実体を実装している。

class VersionInformationScreenViewModel: BaseViewModel {
    private let firebaseProvider: FirebaseProviderProtocol = FirebaseProvider()
// ...

次のようなエラーが発生した。

Use of protocol 'FirebaseProviderProtocol' as a type must be written 'any FirebaseProviderProtocol' Replace 'FirebaseProviderProtocol' with 'any FirebaseProviderProtocol'

このエラーは、プロトコル型の使用方法の変更に伴うもので、プロトコル型を指定する際に any キーワードを使用する必要がある。そのため、以下のように修正した。

class VersionInformationScreenViewModel: BaseViewModel {
    private let firebaseProvider: any FirebaseProviderProtocol = FirebaseProvider()
// ...

GlobalConcurrency を有効にするとワーニングになった

「-enable-upcoming-feature GlobalConcurrency」を有効にすると、以下のようなシングルトン実装している箇所で警告が発生した。

final class AppState: ObservableObject {
    static let shared = AppState()
// ...

以下のような警告である。

Static property 'shared' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor; this is an error in Swift 6

この警告は、グローバルな共有状態に関する Swift 6の新しいルールに起因しており、Sendable プロトコルの遵守するか、グローバルアクターへの明示的な隔離が必要なようだ。問題の解決ができていないが、ひとまず nonisolated(unsafe)アノテーションを付与することで静的診断を回避した。

final class AppState: ObservableObject {
    nonisolated(unsafe) static let shared = AppState()
// ...

きちんと Sendable に準拠させた形で実装したいと思う。

まとめ

Swift 6 やそれ以降のバージョンのSwiftの導入に向けて、現在の最新版である Xcode 15.3 で先行対応できることを紹介した。Xcode の新しいバージョンが出てから慌てるのではなく、あらかじめ対応にアタリをつけておきたいと思う。

有効にしていない機能はまだたくさんあるため、引き続き対応を進めていきたい。

参考記事