こんにちは、開発部第一グループ山崎です。
iOSアプリ開発について、Apple 公式新情報が発表されるWWDC2024の中から、Xcode16の新機能 セッションについてキャッチアップしてみました。
検証環境はXcode16 Beta 5, MacOS Sonoma 14.6 となります。
Edit
Xcode16 new warnings
project > Build Settings > upcoming で検索すると、Swift6からの新しい警告が表示されるようになりました。YESにすると、有効化できます。
サンプルプロジェクトで、全て有効化してみた結果、いくつかのエラーが出たので共有します。
Introduce Existential any
Cancellableなど、いくつかの型(プロトコル)を持った変数を宣言する際、Cancellableという型名の前に「any」をつけろと言われました。こちらは「Introduce Existential any」と呼ばれる新たな機能で、存在型(Existential Type)としてプロトコルを指定する場合は、「そのプロトコルに適合した何某かの型」という意味合いでanyマークをつけなければなりません。Existential Typeとは、変数の型や、メソッドに渡す引数の型や、ジェネリクス型の括弧に囲まれた型などを言います。
private var subscription: any Cancellable func doSomething(param: any MyProtocol) { } class MyClass<T: any MyProtocol> { }
ただし、ライブラリSwiftGen で自動生成されるファイルについて、この対応がなされていないようで、警告が出てしまいました。このファイルは自動生成されるファイルですから、開発者側で勝手にいじるべきところではありません。SwiftGen側でこの件の対応がなされるまでは、「Existential any」の警告はOFFのままにしたほうがいいかもしれません。
Default Internal Imports
「Default Internal Imports」という警告をONにしたところいくつかコンパイルエラーとなりました。
これは、importにアクセスレベルを設定できる新機能のようです。例えば、UIKitなどを普通にimportしていると、internalレベルでのimportという扱いとなります。明示的に public importしていないことになるのに, にもかかわらずUIKitの中のクラスなどをpublic extensionで使おうとしておりエラーとなってしまいました。UIKitをpublic importに変更すると解決しました。この機能は、 importされたシンボルがpublic, privateなどどのような範囲からアクセスできるかというのを指定できる機能ということです。
import UIKit // This is an internal import public extension UICollectionView {} // Error
public import UIKit public extension UICollectionView {} // OK!
Strict Concurrency Checking
こちらはXcode14からあるようですが紹介いたします。Swiftの新機能であるConcurrency Checkingを有効化する設定です。これは、あるデータが同時に読み書きされた場合にデータ競合を発生させないよう、コンパイラの段階でチェックしてくれる機能です。
まだApple はSwift6対応を対応を強制している訳ではなさそうですので、Xcode16にしてもSwift5のままにしておくことは可能です。
しかし、この機能を有効化し、警告が出るような箇所は、Swift6に移行した場合にコンパイルエラーとなってしまう場合があります。試してみたところ、全てコンパイルエラーになるとは限らないようでした。皆さんが対応される場合は、一度試しにSwift6をオンにしてみて、コンパイルエラーになるような箇所から対応されることを勧めます。
さて、Concurrency Checkingは三段階存在します。
最も厳格なCompleteで警告が発生しなければ、Swift6にも安全に移行できるようです。ただ、現段階ではXcode16がBetaのため、警告が出る箇所が多少変動することもあるようです。
Minimal Sendableに適合させたい型が、実際にはSendableに適合できない場合、警告が発生します。例えば、ストアド・プロパティを持つクラスはSendableに適合しません。
※ Sendableとは、複数スレッドからアクセスされても、データ競合が発生しないことが保証されていることを表す新たなプロトコルです。
Targeted Minimal設定のチェックに加えて、コンパイラは、Taskクロージャやasync letのようなSwiftの並行処理が使われている場合にも警告を出します。例えば、Taskクロージャは、Sendableプロトコルに適合していない変数を内部へキャプチャできません。そのため、コンパイラーはその箇所で警告を出すようになります。
- Complete Targeted 設定のチェックに加えて、コンパイラはデータ競合が起こる可能性のあるあらゆる場所で警告を出すようになります。例えば、DispacthQueue.main.async {}は、元のコンテキストとは異なる新しいスレッドを開始するため、データ競合を引き起こす可能性があり、警告が発生します。
具体的な警告と解消例
- ViewModelをSwiftUIのviewのbodyプロパティの中から参照していたところ、
sending ‘self.viewModel’ risks causing data races: sending task-isolated ‘self.viewModel’ to nonisolated callee risks causing data races between nonisolated and main actor-isolated uses
との警告が出ました。ViewModelの宣言のところに「@MainActor
」をつけたところ、解消しました。万一、ViewModelが複数箇所から参照された場合のデータ競合を警告する趣旨と思われます。
@MainActor class ContentViewModel { // do something }
Call to main actor-isolated instance method “foo()” in a synchronous nonisolated context; this is an error in the Swift 6 language mode
との警告が出ました。あるメソッドfoo()がnonisolated(隔離されておらず、データ競合が起こりうる状態)で使われているとの警告のようです。foo()メソッドの定義されているクラスは@MainActorとなっておりこのメソッド自体はisolatedなのですが、呼び出し側がnonisolated状態であったようです。
以下のように、Taskで該当メソッドを囲むと解消しました。
Task { await foo() }
Taskで囲むことで、データ競合から守られ、非同期的に実行されることが保証されたので、解消されたようです。また、以下のように@MainActorを付与し、データ競合から守られる旨を明示することでも解消しました。
Task { @MainActor in foo() }
Static property ‘shared’ is not concurrency-safe because non-’Sendable’ type ‘MyClass’ may have shared mutable state; this is an error in the Swift 6 language mode
との警告が出ました。
また、以下のような警告も追加で出ていました:
Class ‘MyClass’ does not conform to the ‘Sendable’ protocol Annotate ‘shared’ with ‘@MainActor’ if property should only be accessed from the main actor Disable concurrency-safety checks if accesses are protected by an external synchronization mechanism
Sendableに適合していないクラス MyClass にstaticプロパティがある場合、データ競合が発生する可能性があると警告されています。
MyClassにSendableを付けてみました。
すると、さらに、クラスに 「final 」修飾子を付け、またMyClassのすべてのストアドプロパティの型もSendableにすべきだと警告が新たに出現します。
困った点が一つあり、このストアドプロパティの型のいくつかはライブラリMoyaの中で定義されたものでした。そのため、これらの型に自分でSendable適合性を後から追加することはできませんでした。そこで、「@preconcurrency
」マークを 「import Moya
」の部分に追加することで対処しています。このマークは、今のところ、Moyaに由来するクラスがSendableに適合していなくても、警告が出ないよう抑制してくれます。
また、さらに困った点として、ストアドプロパティがlazy var
である場合、単純には解決できませんでした。いくつか対処法がありえます。lazy var はデータ競合を引き起こす可能性があるので、もはや使用を諦めて、let を使って単純な定数とすることもできます。また、構造体を自分で定義した上で、シングルトンオブジェクトのような仕組みを実装することで、Sendable適合性を確保しつつlazyのような動きを維持できます。ここでは、そこまで実装するのが面倒でしたので、便宜上letを選びました。
最終的なコードは以下のようになりました。
Before:
import Moya class MyClass { static let shared = MyClass() lazy var foo: Foo = { return Foo() } }
After:
@preconcurrency import Moya final class MyClass: Sendable { static let shared = MyClass() let foo: Foo = Foo() }
@Previewable
Previewableマクロを追加すると、@Stateの変数を記載できるとのことです。
#Preview { @Previewable @State var foo: String = "foo" MyView(foo: $foo) }
PreviewModifier
例えば、Viewの出現時にサーバーにアクセスするようにしている場合、プレビューするごとに毎回アクセスするのはまずいので、プレビューする際はサーバーにアクセスしないよう振る舞いを変更する、といったプレビュー時だけ振る舞いを変更するなどの用途の模様です。
Build
Explicit Modules
Swiftではデフォルトオフなため、Project > Build Settings > Explicitly Build Modules からYESを選択すると、この機能がONになります。
ビルドのログにおいて、今までは
Compiling XXX…
と表示されていたものが、
Scan XXX…
Compile XXX…
などと分けて表示され、どこで失敗したかがわかりやすくなっているとのことです。ビルドのプロセスでどこにどの程度の時間がかかっているか、グラフで閲覧できる。とのことですが、Xcodeのどのボタンを押すとそのようなグラフが見れるのかいまいちわかりませんでした。 別ビデオ 明示的にビルドされたモジュールについて を見てもやり方は載っていませんでした。Xcode16正式版ではどうなっているかをチェックしようと考えています。
Debug
- Mac OS Sequoia 以降かつiOS 18以降でのビルドの場合は、DWARF5というデバッグシンボルが採用され、処理が高速になるとのことです。
- Firebase Crashlyticsにデバッグシンボルをアップロードしていますが、これ以降アップロードし直しになるのでしょうか? 適宜続報を待ちたいところです。
- Xcode > Organizerでの新機能
- Launchという新しいカテゴリ。 アプリ起動時に時間がかかっていれば、分析してくれるようです。
- Disk Writes というカテゴリは元からあったが、アプリのバージョンアップに従ってある事象が多くなってきたか少なくなってきたかを示す矢印が追加されたとのことです。(ランキングの中で順位が上がったか下がったかを示す矢印のようなもの)
- ※ そもそもOrganizerとは?という方向けの補足です。
- App Storeにアプリをアップロードし公開していれば、基本的にOrganizerに自動で情報が収集されます。自分のXcodeでビルド・アーカイブしたことがなくても、自分のXcodeでログインしているApple IDにて公開されたアプリ、あるいはそのApple IDが招待してもらっているApple Developer Programにて公開されたアプリであれば収集される模様です。Firebase Crashlyticsよりも豊富な情報が得られ、調査の際にはおすすめです。
- ディスク書き込みなどで時間がかかっている点について、Xcodeで紫色で警告が表示され、どのコードが該当するか表示されるようになるとのことです。
- クラッシュ時など、クラッシュに至るまでどこのファイルのどのコードをたどってきたかが上下に順番に表示されるようになり、少々見やすくなったと思います。以下はサンプルアプリでクラッシュさせてみた例。
Test
Swift Testing
今までのXCTestとは違い、アノテーションマーク(”@Test” など)を使ったモダンかつ簡潔な書き方ができるテストとのことです。
- ファイル新規追加から選ぶことができます。今までのXCTestも選べます
- サンプルアプリでテストを書いてみた一例です
- XCTestよりは少し簡潔に書けている(気がする)
- Xcode15ではなかった、テストカバレッジが自動で表示されており便利だと思いました
その他Swift Testingは他にもいろいろな機能がありますので、本格的に把握したい場合は以下のセッションをご覧ください: Go further with Swift Testing(Youtube)
Profile
Instruments > Time Profiler > Flame Graph で、どのコードに時間がかかっているか、Xcode16から見れるようになっているとのことです。下記の赤で囲んだボタンを押すとグラフが閲覧可能になります。
参考
- Xcode16の新機能
- 明示的にビルドされたモジュールについて
- Introduce Existential any
- Default Internal Imports
- Qiita-Swift Concurrency Check and “@preconcurrency import” to handle Swift Concurrency(in Japanese)
- Stack Overflow — Call to main actor-isolated instance method XXX in a synchronous nonisolated context
- Apple Developer Forums — Swift 6 concurrency — Xcode 16b5 — init() is always nonisolated?
- ZOZO-tech blog(in Japanese)
- Lazy var + non-Sendable = false positive actor isolation warning?
- Stack Overflow — Swift — is lazy var thread-safe?