ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
SwiftDataをさらに深く
あなたのアプリで SwiftData を活用する方法を学びましょう。アプリのデータを永続化するために ModelContext と ModelContainer がどのように連携するのかをご覧ください。変更を手動で追跡し、FetchDescriptor、SortDescriptor、enumerateでSwiftDataを大規模なアプリに使用する方法を紹介します。 このセッションを最大限に活用するために、まずWWDC23の "Meet SwiftData "と "Model your schema with SwiftData "を見ることをお勧めします。
関連する章
- 0:00 - Intro
- 3:42 - Configuring persistence
- 7:21 - Track and persist changes
- 11:20 - Modeling at scale
- 14:54 - Wrap-up
リソース
関連ビデオ
WWDC23
-
ダウンロード
♪ ♪
Nick Gillett: こんにちは SwiftDataチームのエンジニアの Nick Gillettです このセッションではSwiftDataで作られた アプリがこの豊かで強力な新しい フレームワークを活用するために どのように進化していくかを 詳しくみながら アプリでどのように 永続性を設定するかを検証します 次にModelContextを使用して 変更を追跡し 永続化する方法について説明します 最後にコードでオブジェクトを 扱うときの SwiftDataを最大限に 活用する方法を検討します 「Meet Swift Data」と 「Model your Schema with SwiftData」で 紹介された概念とAPIに基づいて このセッションは構成されています 事前にそれらのセッションで おさらいすることを強くすすめます このセッションではSwiftDataを使った アプリの構築がいかに簡単かを示すために 今年 私たちが作った 新しいサンプルアプリの SampleTripsを使います SampleTripsを使えば いつどこでどんな旅をしたいか 簡単に整理することができます SwiftDataは 取り消しや アプリを切り替えたときの自動保存のような 標準的なプラットフォームの機能を 簡単に実行できます SwiftDataはSwiftを使うアプリで データを永続化する新しい方法です クラスや構造体のようなコードで すでに使っている型と 連動するように設計されてます この概念の核となるのは モデルで 新しいマクロ @Model で記述され 永続化したい型について SwiftData に伝えます これは SampleTrips アプリの Trip クラスです トリップに関する情報を取得するための いくつかのプロパティとSampleTripsで 使用される他のオブジェクトへの 参照があります ここでご紹介するケースのように 永続化せずに書くコードと 永続化して書く必要があるコードの間の 最小限の距離感になるように SwiftDataを設計しました ほんの少しの変更で このTripが永続化したいモデルであることを設定し バケットリストアイテムと宿泊施設の リレーションが どのように動作すべきかを 記述しました 可能な限りSwiftDataは あなたが書いたコードから 使用したい構造を自動的に推測します しかしSwiftDataはデータを保存する 方法を正確に記述するための 強力なカスタマイズのセットも 提供しています 「Model your Schema with SwiftData」で モデルのパワーのすべてを学べます Tripクラスへのこれらのアノテーションは SwiftDataで2つの重要な役割を 果たすようになります 1つ目はスキーマと呼ばれるアプリの オブジェクト・グラフを記述することで 2つ目はトリップ・クラスが それに対してコードを書ける インターフェースになることです この二つの役割を果たすことで @Modelマクロで アノテーションされたクラスを SwiftDataを使用するアプリの 中心的な接点にします
スキーマは ModelContainerと 呼ばれるクラスに適用され データがどのように 永続化されるかを記述します ModelContainerは スキーマを消費して Modelクラスのインスタンスを保持できる データベースを生成します コード内でModelクラスの インスタンスを扱う場合 それらのインスタンスは メモリ内の状態を追跡し 管理するModelContextに リンクされます この二元性はSwiftDataの中核であり このセクションでは 永続性の構造とModelContainerで どのように動作するかを 説明するために モデルの最初の役割を 詳しく見ていきます ModelContainerは データがどのようにデバイスに保存されるか または永続化されるかを 記述する場所です ModelContainerは スキーマとその永続化の 橋渡し役であると考えることができます オブジェクトがどのように保存されているのか たとえばメモリ上か、ディスク上なのか また、バージョニング マイグレーション グラフ分離といった 操作性と進化するセマンティクスとが 融合する場所です スキーマでコンテナを インスタンス化するのは単純です 私は作業したい型だけ 提供することができ SwiftDataはスキーマの 残りの部分を把握します 例えばTrip クラスは他のモデル・タイプと 関連しているため次のようになります ModelContainerは単一の型を 渡しただけなのに 実際にこのスキーマを推論します ModelContainerは ModelConfigurationと 呼ばれるクラスを使って より複雑な設定を可能にするために コードとともに成長するように 設計された 他にも多くの強力な イニシャライザを持っています ModelConfiguration は スキーマの永続性を記述します 一過性のデータであればメモリへ 永続的なデータであればディスクに データの保存場所を制御します ModelConfigurationは あなたが選択した特定のファイル URLを使用することができ グループコンテナEntitlementのような アプリのEntitlementを使用して 自動的に生成することもできます 構成は機密データやテンプレな データへの書き込みを防ぐために 専用モードとして 永続性ファイルをロードするといった 記述することもできます そして最後に 複数のCloudKitコンテナを使用するアプリは スキーマのModelConfigurationの 一部として指定できます 例えば 新しいPersonクラスと Addressクラスを使用して SampleTripsに連絡先情報を 追加しようとしていると仮定します まず使用したいすべての型を含む トータル・スキーマを定義します 次にTripバケツリストと宿泊施設 の 各モデルを含む SampleTrips データの構成が定義されます この特定のオブジェクトグラフの データを保存するために使用したい ファイルのURLとSampleTripsのデータを CloudKitに同期するときに使用したい CloudKitコンテナのコンテナ識別子を 宣言します 次にPersonとAddressを含む 新しいスキーマのモデルは Tripsグラフから分離するために 一意のファイルURLと CloudKitコンテナ識別子を持つ 独自の設定のなかで 定義されます 最後にスキーマと設定が 組み合わさることで ModelContainerが形成されます
ModelConfigurationのパワーを使えば アプリの永続性要件が どんなに複雑であっても 簡単に記述することができます 手作業でコンテナを インスタンス化するとともに SwiftUI アプリは 新しいmodelContainer修飾子を使用して 作業したいコンテナを作成できます modelContainerモディファイアは アプリ内の任意のViewやSceneに追加ができ シンプルなものから パワフルなものまで あらゆるModelContainerに 対応しています このセクションではスキーマと ModelContainerを使用した 永続性を組み合わせる方法を 紹介しました より強力な機能やオブジェクト・ グラフを構築するにつれて アプリとともに成長します そしてModelConfigurationを 使用して強力な 永続化機能を解放する 方法を示しました 「Meet SwiftData」で 学んだように ユーザインターフェイスを書いたり モデルオブジェクトを操作するときに ModelとModelContextは 最も頻繁に使われる2つの概念です このセクションでModelContextが どのように変更を追跡し ModelContainerを通じて 更新の永続化について深く掘り下げていきます ViewやSceneコードでmodelContainer モディファイアを使用すると アプリの環境を特定の方法で準備し この修飾子は環境の新しい modelContext キーを コンテナのメインコンテキストに バインドします mainContextは SceneやViewでModelObjectを 扱うための特別なMainActorに 沿ったモデルコンテキストです 環境からモデルコンテキストを 使うことでViewコードは クエリによって使われる コンテキストに簡単にアクセシブルとなり ここで削除のようなアクションを 実行できるようになります それでは実際に何が 実行可能なのでしょうか ModelContextはアプリが管理する データのViewと考えられます 扱いたいデータは使用されるたびに モデルコンテキストに取り込まれます SampleTripsでは 近日予定のトリップViewが リストのデータをロードすると各トリップオブジェクトが メインコンテキストに読み込まれます トリップが編集されるとその変更は スナップショットとして モデル・コンテキストに記録されます 新しいTriporの挿入や既存の Triporの削除など他の変更が 行われるとコンテキストは 「context.save() 」を呼び出すまで これらの変更に関する状態を 追跡し維持します これは削除されたトリップが リストに表示されなくなったとしても、 save を呼び出して削除が永続化 されるまではModelContext 内に
存在することを意味し save が呼び出されるとコンテキストは モデルコンテナを永続化し その状態をクリアします オブジェクトをリストで表示するなど コンテキストのオブジェクトを まだ参照している場合は その処理を終えるまで オブジェクトは コンテキストの中に存在します その時点で彼らはフリーになり コンテキストが空になります ModelContext はバインドされている ModelContainer と協調して動作し Viewで取得した オブジェクトを追跡し 保存が実行されると 変更が反映されます ModelContextはまた必要に応じて キャッシュされた状態を クリアするためのロールバックや リセットのような機能もサポートして 取り消しや自動保存のような プラットフォーム機能に 対応した理想的なクラスです SwiftUI アプリでは modelContainer修飾子は コンテナのmainContextに WindowsのundoManagerをバインドする isUndoEnabled 引数を持っています つまりメイン・コンテキストで 変更が加えられると、 3本指スワイプやシェイクなどの システム・ジェスチャーを使って 追加のコードなしで変更を 取り消したりやり直したりができ ModelContext はモデル オブジェクトに変更が加えられると 取り消しとやり直しのアクションを 自動的に登録します modelContainer修飾子は 環境のundoManagerを使用します undoManagerは通常 ウィンドウまたはウィンドウグループの一部として システムによって提供されます ModelContextがサポートする 他の標準的なシステム機能は自動保存です 自動保存を有効にするとアプリの フォアグラウンドやバックグラウンドへの 移動などのシステムイベントに応じて モデルのコンテキストが保存されます またアプリが使用されるとメイン コンテキストも定期的に保存されます 自動保存はアプリのデフォルトで 有効になっていてmodelContainer モディファイアの isAutosaveEnabled引数を 使用して 必要に応じて無効にできます 手作業で作成された モデルコンテキストは無効になります 「Meet SwiftData」では アプリでModelContextを扱う方法と SwiftUIとの組み合わせについて 多くを学びました しかしアプリがモデル・オブジェクトを 扱う必要があるのは ユーザー・インターフェースだけでは ありません このセクションではSwiftDataが どのくらい強力で拡張性のある コードをこれまで以上に簡単かつ安全に 書けるのかを検証してみましょう バックグラウンド・キューでのデータ処理 リモート・サーバーや他の永続化 メカニズムとの同期バッチ処理などのタスクは すべてモデル・オブジェクトを扱うもので セットやグラフで処理されることが多いです これらのタスクの多くは ModelContextの fetchメソッドでオブジェクトを 取得することから始まります この例ではTripモデルの FetchDescriptorはtrips配列が オブジェクトのコレクションになることを Swiftに伝えます キャスティングや複雑な結果に 悩まされることはありません
新しい述語マクロを使用して 複雑なクエリを 簡単に作成できます 例えば特定のホテルに 泊まる旅行とは? あるいはまだ予約が必要な アクティビティがある旅行は? SwiftDataではサブクエリと結合を サポートする複雑なクエリはすべて シンプルなswiftで 書くことができます Predicateをデータベースクエリに変換するために Predicateは作成したモデルを使用し SwiftDataはモデルから生成された スキーマを使用します FetchDescriptorは 新しい述語マクロを スキーマと組み合わせることで コンパイラで検証されたクエリを Appleプラットフォーム上の永続性に はじめてもたらします FetchDescriptorおよび関連クラス ソート・スクリプタのような関連クラスは、 一般クラスを使用して結果型を形成し 使用できるモデルのプロパティを コンパイラに伝えます 多くのチューニング・オプションがある オフセットやリミットなど お馴染みのチューニング・オプションが 多数用意されてます フォールトやプリフェッチ用の パラメータもあります
この機能はすべて 新しい関数に集約されています これはプラットフォームのbest practicesを 単一のコール・サイトで カプセル化することによって バッチ・トラバーサルと 列挙のfoiblesomeパターンを 暗黙に効率化するのを助けるように 設計されています EnumerateはFetchDescriptorsの 複雑さに関係なく シンプルなものから強力なものまで そしてその中間にあるもの すべてでうまく機能します Enumerateは バッチングや変異ガードといった トラバーサルのプラットフォームの best practicesを自動的に実装します これらは特定の利用ケースに 合わせてカスタマイズ可能です 例えばenumerateが使用する バッチ・サイズのデフォルトは 5000オブジェクトです しかしメモリの増加を犠牲にして トラバーサル中のI/O操作を減らすため 10,000に変更することもできます 画像を含むグラフなど 重いオブジェクトのグラフ 画像ムービーやその他の大きなデータブロブを 含むようなグラフでは より小さなバッチサイズを使用できます バッチサイズを小さくすると しかし列挙中のI/Oは増加します Enumerateはまた変異ガード も デフォルトで含まれている 大規模なトラバーサルで パフォーマンスの問題が発生します 最も頻繁な原因の1つは列挙中に コンテキストにトラップされる変異で allowEscapingMutations が 設定されていない場合 列挙を実行する ModelContext が ダーティであることがわかると enumerate がスローされ すでにトラバースされたオブジェクトを 解放することができなくなります このセッションではスキーマと ModelConfigurationを使用して 強力な永続性設定を 作成する方法を学びました またModelContainerと ModelContextを使って 取り消しややり直しのような 標準的なシステムプラクティスがいかに 簡単に採用できるかも学びました そして今日SwiftDataを使って FetchDescriptorや Predicate Enumerateで プロジェクトでかつてないほど安全で パフォーマンスの高いコードを書けます 今後 数ヶ月数年の間にあなたが この新しいフレームワークで 可能性の限界に挑戦する姿を 見るのが待ち遠しいです ご視聴ありがとうございます コーデイングを楽しんでください
-
-
1:45 - Trip model with cascading relationships
@Model final class Trip { var destination: String? var end_date: Date? var name: String? var start_date: Date? @Relationship(.cascade) var bucketListItem: [BucketListItem] = [BucketListItem]() @Relationship(.cascade) var livingAccommodation: LivingAccommodation? }
-
4:21 - Initializing a ModelContainer
// ModelContainer initialized with just Trip let container = try ModelContainer(for: Trip.self) // SwiftData infers related model classes as well let container = try ModelContainer( for: [ Trip.self, BucketListItem.self, LivingAccommodation.self ] )
-
5:41 - Using ModelConfiguration to customize ModelContainer
let fullSchema = Schema([ Trip.self, BucketListItem.self, LivingAccommodations.self, Person.self, Address.self ]) let trips = ModelConfiguration( schema: Schema([ Trip.self, BucketListItem.self, LivingAccommodations.self ]), url: URL(filePath: "/path/to/trip.store"), cloudKitContainerIdentifier: "com.example.trips" ) let people = ModelConfiguration( schema: Schema([Person.self, Address.self]), url: URL(filePath: "/path/to/people.store"), cloudKitContainerIdentifier: "com.example.people" ) let container = try ModelContainer(for: fullSchema, trips, people)
-
6:49 - Creating ModelContainer in SwiftUI
@main struct TripsApp: App { let fullSchema = Schema([ Trip.self, BucketListItem.self, LivingAccommodations.self, Person.self, Address.self ]) let trips = ModelConfiguration( schema: Schema([ Trip.self, BucketListItem.self, LivingAccommodations.self ]), url: URL(filePath: "/path/to/trip.store"), cloudKitContainerIdentifier: "com.example.trips" ) let people = ModelConfiguration( schema: Schema([ Person.self, Address.self ]), url: URL(filePath: "/path/to/people.store"), cloudKitContainerIdentifier: "com.example.people" ) let container = try ModelContainer(for: fullSchema, trips, people) var body: some Scene { WindowGroup { ContentView() } .modelContainer(container) } }
-
7:40 - Using the modelContainer modifier
@main struct TripsApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: Trip.self) } }
-
7:50 - Referencing a ModelContext in SwiftUI views
struct ContentView: View { @Query var trips: [Trip] @Environment(\.modelContext) var modelContext var body: some View { NavigationStack (path: $path) { List(selection: $selection) { ForEach(trips) { trip in TripListItem(trip: trip) .swipeActions(edge: .trailing) { Button(role: .destructive) { modelContext.delete(trip) } label: { Label("Delete", systemImage: "trash") } } } .onDelete(perform: deleteTrips(at:)) } } } }
-
9:57 - Enabling undo on a ModelContainer
@main struct TripsApp: App { @Environment(\.undoManager) var undoManager var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: Trip.self, isUndoEnabled: true) } }
-
11:05 - Enabling autosave on a ModelContainer
@main struct TripsApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: Trip.self, isAutosaveEnabled: false) } }
-
11:54 - Fetching objects with FetchDescriptor
let context = self.newSwiftContext(from: Trip.self) var trips = try context.fetch(FetchDescriptor<Trip>())
-
12:14 - Fetching objects with #Predicate and FetchDescriptor
let context = self.newSwiftContext(from: Trip.self) let hotelNames = ["First", "Second", "Third"] var predicate = #Predicate<Trip> { trip in trip.livingAccommodations.filter { hotelNames.contains($0.placeName) }.count > 0 } var descriptor = FetchDescriptor(predicate: predicate) var trips = try context.fetch(descriptor)
-
12:27 - Fetching objects with #Predicate and FetchDescriptor
let context = self.newSwiftContext(from: Trip.self) predicate = #Predicate<Trip> { trip in trip.livingAccommodations.filter { $0.hasReservation == false }.count > 0 } descriptor = FetchDescriptor(predicate: predicate) var trips = try context.fetch(descriptor)
-
13:18 - Enumerating objects with FetchDescriptor
context.enumerate(FetchDescriptor<Trip>()) { trip in // Operate on trip }
-
13:36 - Enumerating with FetchDescriptor and SortDescriptor
let predicate = #Predicate<Trip> { trip in trip.bucketListItem.filter { $0.hasReservation == false }.count > 0 } let descriptor = FetchDescriptor(predicate: predicate) descriptor.sortBy = [SortDescriptor(\.start_date)] context.enumerate(descriptor) { trip in // Remind me to make reservations for trip }
-
14:01 - Fine tuning enumerate with batchSize
let predicate = #Predicate<Trip> { trip in trip.bucketListItem.filter { $0.hasReservation == false }.count > 0 } let descriptor = FetchDescriptor(predicate: predicate) descriptor.sortBy = [SortDescriptor(\.start_date)] context.enumerate( descriptor, batchSize: 10000 ) { trip in // Remind me to make reservations for trip }
-
14:28 - Fine tuning enumerate with batchSize and allowEscapingMutations
let predicate = #Predicate<Trip> { trip in trip.bucketListItem.filter { $0.hasReservation == false }.count > 0 } let descriptor = FetchDescriptor(predicate: predicate) descriptor.sortBy = [SortDescriptor(\.start_date)] context.enumerate( descriptor, batchSize: 500, allowEscapingMutations: true ) { trip in // Remind me to make reservations for trip }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。