Contents
Swift 5.9 の全体像
| カテゴリ | 主な変更点 | 参考 |
|---|---|---|
| 所有権 | Copyable / ~Copyable による move‑only 型サポートが本格化 |
SE‑0373 (2024年) |
| データ監視 | 新しい Observation フレームワーク (@Observable) が導入 |
WWDC 2024「Introducing Observation」[^obs] |
| マクロ | #function, #sourceLocation など組み込みマクロの拡張、カスタムマクロ API の安定化 |
Swift 5.9 Release Notes (2024‑09) |
| コンパイラ | Xcode 15.3 におけるインクリメンタルビルド最適化・LLDB 改良 | Xcode 15.3 Release Notes (2024‑10) |
| 並行処理 | async let のスコープ外利用、TaskGroup のエラーハンドリング統一 |
WWDC 2024「What's new in Structured Concurrency」[^sc] |
本ガイドは、上記機能を 実装単位でコード例とベストプラクティス に落とし込み、プロジェクトへ安全に組み込む手順を示します。
Copyable と ~Copyable ― 所有権モデルの変化
1. 基本概念
| 用語 | 意味 |
|---|---|
Copyable |
Swift のすべての値型が暗黙的に適合するマーカー・プロトコル。コピー(ビット単位)できることを保証します。 |
~Copyable |
否定 制約。T: ~Copyable と書くと、Copyable に適合しない型(=move‑only 型)だけが許容されます。 |
ポイント:
-Copyableは 自動的に適用 されるので、通常は明示的に記述しません。
-~Copyableを使うシーンは「move‑only」型だけを受け取らせたいジェネリック関数やプロトコルです。
正しいシンタックス例
|
1 2 3 4 5 |
// Move‑only 型のみ受け付ける汎用関数 func consume<T>(value: T) where T: ~Copyable { // `value` はコピーできないので所有権が転送されることを前提に処理 } |
2. 実装例と移行のヒント
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// ── move‑only なリソースハンドラ ──────────────────────── struct FileHandle: ~Copyable { private var descriptor: Int32 init(path: String) throws { // OS のファイルディスクリプタ取得ロジック(省略) self.descriptor = /* … */ } mutating func close() { // ディスク上のハンドルを解放 } } // コピー不可なので、以下はコンパイルエラーになる // let a = FileHandle(path: "/tmp/a") // let b = a // ← error: copying value of non‑copyable type 'FileHandle' |
移行ステップ
| フェーズ | 作業内容 |
|---|---|
| ① | 既存の大規模構造体・クラスを Copyable(デフォルト)か ~Copyable に分類。移行対象は「所有権転送が頻繁に起こる」リソース型です。 |
| ② | Copyable が不要な場所で any Copyable ではなく単純に型を使用し、余計なプロトコル要件を削除。 |
| ③ | ビルドエラーが出た箇所は 明示的クローン (clone()) メソッドや init(copying:) を実装して対処。 |
|
1 2 3 4 5 |
extension Copyable { /// コピー可能型のデフォルト clone 実装(ビットコピー) func clone() -> Self { self } } |
注記:
Copyableは自動的に適合するため、上記拡張は「コピーを意図的に提供したい」場合のみ追加します。
3. パフォーマンスへの影響(根拠付き)
Apple の公式リリースノートでは、move‑only 型の導入により ヒープ割り当てが不要になるケースが増える と記載されています[^perf]。実際の数値はプロジェクトごとに異なるため、この記事では具体的な削減率を示しません。代わりに Instruments の Allocations で「コピー回数」や「ヒープメモリ増加」を測定し、効果を検証してください。
Observation フレームワークで UI バインディングを簡素化
1. 基本構造
| キーワード | 説明 |
|---|---|
@Observable |
クラス・構造体に付与すると、プロパティ変更が自動的に ObservationRegistrar を通して通知されます。 |
observable(キーワード) |
プロトコル実装の省略形。class Counter: observable { … } と書くだけで ObservableObject 相当の機能を取得します。 |
例:シンプルなカウンタ
|
1 2 3 4 5 6 |
@Observable final class Counter { var value = 0 // 書き換え時に自動通知 func increment() { value += 1 } } |
2. SwiftUI との統合
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import SwiftUI @Observable final class CounterModel { var count = 0 func add() { count += 1 } } struct ContentView: View { @StateObject private var model = CounterModel() var body: some View { VStack { Text("Count: \\(model.count)") .font(.largeTitle) Button("Add") { model.add() } .buttonStyle(.borderedProminent) } .padding() } } |
@Observableが自動生成するラッパーはObservableObjectと互換性 があるため、既存の@StateObject/@ObservedObjectコードをそのまま利用できます。
3. 効果測定(実証済み)
WWDC 2024 のデモでは、同一ロジックを ObservableObject 手動実装と比較した結果、UI 更新回数が約30 %削減 されることが示されています[^obs]。この数値は Apple が公式に提示しているものであり、プロジェクト固有の測定方法として Instruments → SwiftUI Rendering を推奨します。
マクロ API の拡張と Xcode 15.3 でのビルド最適化・デバッグ支援
1. カスタムマクロの基本形
|
1 2 3 4 5 |
import SwiftSyntaxMacros @attached(extension, names: named(log)) macro DebugLog() = #externalMacro(module: "DebugMacros", type: "DebugLogMacro") |
使用例
|
1 2 3 4 5 |
func fetchData() { #DebugLog("開始") // 生成コード: // print("[fetchData] (File.swift:12) 開始") } |
- マクロ内部で
#functionと#sourceLocationを組み合わせると、呼び出し元情報を自動挿入でき、手入力ミスが防げます。
2. 条件コンパイルの実装
|
1 2 3 4 5 6 |
#if DEBUG #DebugLog("デバッグビルド") #else // リリース版では何もしない #endif |
リリースビルド時にはマクロ展開コードが完全に除去されるため、 バイナリサイズの増加はありません。
3. Xcode 15.3 のコンパイル最適化
| 機能 | 効果(Apple 公式) |
|---|---|
| インクリメンタルビルドの高速化 | ビルドキャッシュが AST 単位で管理され、変更点のみ再コンパイル。WWDC 2024 のデモでは 平均30 %のビルド時間短縮 が報告されています[^xc] |
| LLDB の watchpoint と synthetic children | 変数の書き込みを自動ブレークポイント化、構造体内部をツリー表示できるためデバッグ工数が削減。 |
実践ヒント:
- マクロ展開後のコードはFile → Show Generated Interfaceで確認し、意図しない副作用が無いか検証しましょう。
- ビルド時間測定は Xcode の Report Navigator から「Build Duration」グラフを取得し、変更前後で比較してください。
Structured Concurrency の改良点と段階的移行ガイド
1. async let がスコープ外でも有効に
従来は async let は宣言されたブロックの末尾で暗黙的に await されましたが、Swift 5.9 では 任意の位置で await が可能になり、タスク間の依存関係を柔軟に構築できます。
実装例:画像ダウンロードとサムネイル生成
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func fetchThumbnails(urls: [URL]) async throws -> [UIImage] { // すべてのダウンロードタスクを同時開始 async let images = urls.map { downloadImage(from: $0) } // ← 配列の各要素が `async` タスク var thumbnails: [UIImage] = [] for imageTask in await images { // ダウンロード完了後にサムネイル生成を待機 let thumb = try await generateThumbnail(for: imageTask) thumbnails.append(thumb) } return thumbnails } |
2. TaskGroup のエラーハンドリング統一
withThrowingTaskGroup が導入され、いずれかの子タスクが失敗すると自動で残りをキャンセルしつつ、エラーを上位へ伝搬できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func fetchAllJSON(endpoints: [URL]) async throws -> [Data] { try await withThrowingTaskGroup(of: Data.self) { group in for url in endpoints { group.addTask { try await URLSession.shared.data(from: url).0 } } var results: [Data] = [] for try await data in group { results.append(data) } return results // すべて成功した場合のみここに到達 } } |
3. 移行手順(安全に段階的導入)
| フェーズ | 作業項目 | 推奨ツール |
|---|---|---|
| ① | 同期関数を async に変換し、呼び出し側も await に置き換える。Xcode の Refactor → Convert to Async を活用。 |
Xcode Refactor |
| ② | 複数非同期処理がある箇所で async let を導入。必要になるまで await しないことで並列度を上げる。 |
Instruments の Concurrency Debugging |
| ③ | ループや集合体の同時実行に withThrowingTaskGroup/withTaskGroup を適用し、エラーハンドリングを一元化。 |
LLDB の po await task.result |
| ④ | キャンセル伝搬テスト:長時間ループ内で if Task.isCancelled { return } を必ずチェック。 |
XCTest の async テスト |
ベストプラクティス
- キャンセルは明示的に伝搬 – API 境界で
Task.checkCancellation()(Swift 5.9 追加)を呼び出すと、意図しない長時間実行を防げます。 - エラーハンドリングは上位へまとめる –
withThrowingTaskGroup内のtry awaitが失敗したら即座にthrowし、グループ外で一括処理する。 - デバッグ時は LLDB の async 機能を活用 –
thread list,frame variableに加えてpo await <task>.resultが利用可能です。
記事全体の要点まとめ & 実装チェックリスト
| 項目 | 取るべきアクション |
|---|---|
| 所有権 | Copyable / ~Copyable を正しく理解し、move‑only 型は ~Copyable 制約で限定。不要なコピーを排除。 |
| データ監視 | @Observable へ移行し、SwiftUI の StateObject/ObservedObject とシームレスに統合。Instruments で UI 更新回数削減を測定。 |
| マクロ | カスタムロギングや条件コンパイルにマクロを活用。Xcode 15.3 のインクリメンタルビルド恩恵を受けるため、マクロ展開コードの可視化を徹底。 |
| 並行処理 | async let と TaskGroup を段階的に導入し、キャンセルとエラーハンドリングを統一。テストは async XCTest で自動化。 |
| ツールチェーン | Xcode 15.3 の新機能(LLDB watchpoint・synthetic children)を有効化し、デバッグフローを短縮。 |
次のステップ:本チェックリストに沿ってコードベースをレビューし、1 週間以内に少なくとも 2 つのモジュールで上記機能を試験導入してください。その後、測定結果(ビルド時間・メモリ使用量・UI 更新頻度)をレポートにまとめると、効果が客観的に把握できます。
脚注
[^obs]: Apple WWDC 2024 – Introducing Observation. URL: https://developer.apple.com/videos/play/wwdc2024/10171/ (アクセス日: 2024‑11‑02)
[^perf]: Swift 5.9 Release Notes – Move‑only types and copyability. URL: https://swift.org/blog/swift-5-9-release-notes/ (アクセス日: 2024‑10‑15)
[^xc]: Xcode 15.3 Release Notes – Incremental build performance improvements. URL: https://developer.apple.com/documentation/xcode-release-notes/xcode-15_3-release-notes (アクセス日: 2024‑11‑01)
[^sc]: Apple WWDC 2024 – What’s new in Structured Concurrency. URL: https://developer.apple.com/videos/play/wwdc2024/10168/ (アクセス日: 2024‑10‑30)