ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
SwiftDataでスキーマをモデル化
SwiftDataでスキーママクロとマイグレーションプランを使用して、アプリのより複雑な機能を構築する方法を学びます。Attribute(属性)と@Relationship(リレーションシップ)オプションを使って、永続性を微調整する方法を紹介します。@Transientを使ってデータモデルからプロパティを除外し、スキーマのあるバージョンから次のバージョンに簡単に移行する方法を学びます。 このセッションを最大限に活用するために、WWDC23の"Meet SwiftData"と"Build an app with SwiftData"を最初に見ることをお勧めします。
関連する章
- 0:00 - Intro
- 1:41 - Utilizing schema macros
- 5:30 - Evolving schemas
- 8:56 - Wrap-up
リソース
関連ビデオ
WWDC23
-
ダウンロード
♪ ♪
Rishi: こんにちは Rishi Vermaです このセッションでは SwiftDataでスキーマを構築するモデルを コード化する方法を紹介します まずはスキーママクロを 最大限に活用する方法と アプリの変更に合わせて スキーママイグレーションで スキーマを進化させる方法について 説明します 「Meet SwiftData」と 「Build an app with SwiftData」を 始める前にご覧ください 本コンテンツはそれらのビデオで 紹介された概念に基づいて展開されます SwiftData は データモデリングと管理のための 強力なフレームワークで 最新のSwift アプリを強化します SwiftUIのように 外部ファイルフォーマットを使用せず コードに完全に焦点を当て シームレスなAPI体験を作成するために Swiftの新しいマクロシステムを 使用しています 私はSampleTripsアプリに取り組んでいて ユーザが今後の旅行を 計画できるようにしています 各トリップは名前 目的地 また開始日と終了日を 指定して作成します 旅行には宿泊施設とバケットリストの 項目を含むことができます SwiftDataを追加するのは インポートを追加と同じくらい簡単で Tripを@Modelで飾る これだけです ModelマクロはTripクラスを PersistentModelに適合させ 記述スキーマを生成します 私のモデルを定義するコードは 今や私のアプリのスキーマのための source of truthになっています 僕のトリップモデルの デフォルトの挙動はいいんだけど もう少し微調整できると思います SwiftDataのスキーママクロは 私のアプリのために完璧に動作すべく 永続化エクスペリエンスの動作を カスタマイズすることを可能にします オリジナルのスキーマで アプリを公開したとき 各トリップ名が一意であることを 確認しませんでした このため同じ名前の旅行間で ちょっとした問題が発生し それを解決する必要があります これは 属性スキーママクロと 一意性オプションで修正できます SwiftDataは私のトリップモデルの スキーマを生成し 永続的なバックエンドに保存する すべてのトリップが 独自な名前を持つことを 保証します その名前のトリップがすでに存在する場合 永続的なバックエンドは 最新の値に更新されます これをアップサートと呼びます アップサートはインサートとして始まります 挿入が既存のデータと衝突した場合 それは更新となり 既存のデータのプロパティを更新します 数値 文字列 UUIDのような プリミティブな値型であれば 他のプロパティにも一意制約を 適用できます 私のスキーマはもう少し工夫が必要です 最初に指定した start_dateとend_datethから 厄介なアンダースコアを 削除したいのです 変数名を変更すれば これは生成されたスキーマは 新しいプロパティとみなされます。 ここではSwiftDataに新しいプロパティを 作成させたくありません むしろ既存のデータを そのまま保存したいと思います そうするには単純にAttributeを使って 元の名前をプロパティ名にマッピングし originalName: パラメータを 指定するだけです これらを元の名前からマッピングすることで データの損失を避けることができます これでスキーマの更新を次のリリースで 簡単に移行できるようになります また@Attributeマクロは大きなデータを 外部に保存したり トランスフォーマブルをサポートしたりと さらに多くのことができます
僕の旅は順調に進んでいるようです 今度は項目のリレーションシップに取り組みたいです 私が旅行に新しいバケットリストの項目や 宿泊施設を追加するとき、私のために SwiftDataは暗黙のうちに 私のモデル間のインバースを発見し それらを設定します 暗黙的インバースは 注釈は必要ありません ただ機能するだけです 暗黙的インバースはトリップが 削除されたときに バケットリストの項目と宿泊施設の プロパティを 無効にするデフォルトの削除ルールを 使用します ただしバケットリストの項目と滞在先は 旅行と一緒に削除してほしいです カスケード削除ルールで @Relationship マクロを追加すれば簡単にできます トリップを削除すると それらの関係も削除されます さらに@Relationshipマクロは originalName modifierや to-manyリレーションシップの最小数と 最大数を指定する機能など 多くのことができます SampleTripsアプリはいい感じにだけど まだアップデートが残ってます さらに 旅行を何回見たかを 記録する方法を追加したいです こうすることで休暇を取ることにどれだけ テンションを上げているかを 測ることができます 休みが待ちきれないですね 私はこのビューカウントが SwiftDataによって 永続化されることを望んでいませんので @Transientマクロで 簡単に対処できます プロパティを@Transientで飾るだけで この特定のプロパティは永続化されません こんなに簡単なのです Transientマクロは不要なデータの 永続化を避けるのに役立ちます トランジェント・プロパティには 必ずデフォルト値を指定してください これはSwiftDataから取得されたときに 論理的な値を持つことを保証します これらのスキーママクロを 利用するための詳細な情報については SwiftData のドキュメントを 確認してください SampleTrips アプリのスキーマ は 永続化エクスペリエンスを調整するために いくつかの進化を遂げました アプリがリリースからリリースへの アップデートに対応できるようにする 必要がありプロパティの追加や削除など スキーマに変更を加え データ移行が発生します この移行は厄介なシナリオですが SwiftDataはそれを簡単にしてくれます VersionedSchemaとSchemaMigrationPlan が それを支援します SwiftDataモデルに変更を加えた アプリの新しいバージョンを リリースする準備を するときは いつでも以前にリリースした スキーマをカプセル化する VersionedSchema を定義します スキーマのそれぞれの異なるバージョンは SwiftData がそれらの間で起こった変更を 定義されなければなりません 履歴がわかるようにバージョン管理された Schemaとして定義されなければなりません 次にバージョン化されたスキーマの 順序を繋ぎ合わせ スキーマ移行計画を作成します これによってSwiftDataは 必要なマイグレーションを 順番に実行可能になります 順序付けられたスキーマを 移行計画にレイアウトしたら それぞれの移行ステージを 定義し始めます 移行ステージには2種類あります ひとつめは 軽量マイグレーションです 軽量マイグレーションでは 次のアプリのリリースに向けて 既存のデータをマイグレーションする コードを追加する必要はありません 日付プロパティにoriginalNameを 追加したりリレーションシップの 削除ルールを指定するような修正は 軽量マイグレーションの対象です 旅行名をユニークなものにすることは 軽量マイグレーションの対象には なりません この変更のためにカスタムマイグレーション ステージを作成する必要があり トリップの重複排除を行うことができます 私はまず最初のリリースから スキーマを取り出しそれを バージョン管理されたSchemaに カプセル化することから始めます このスキーマに SampleTripsSchemaV1という 名前を付けます バージョン管理されたスキーマは それぞれが定義するモデル・クラスを リストアップしてます SchemaV2は旅行名の 一意性制約を追加したところです Tripモデルクラスに加えた変更を カプセル化した別の バージョン管理スキーマを作成します SchemaV3でも同じように 開始日と終了日に加えられた 名前の変更をキャプチャしています すべてのバージョン管理スキーマを 取得しました SchemaMigrationPlanを作成し リリースからリリースへの移行を どのように処理するかを記述します 思ったより簡単です ただアプリケーションのスキーマの 総順序を提供するだけです それからどのマイグレーションが軽量か カスタムかを注釈する必要があります V1からV2への移行には データを移行する前に 操作を実行できる カスタムステージが必要です willMigrateクロージャで マイグレーションが起こる前に トリップを重複排除することができます SwiftDataはV1からV2への移行が いつ起こるかを検知し このクロージャーを実行してくれます originalNameの もうひとつのマイグレーションは 軽量なのでそのステージも追加できます マイグレーション計画の詳細が すべて決まったので いよいよマイグレーションを実行します ModelContainerに 現在のスキーマと移行プランを セットアップして完了です 私のユーザはどのバージョンからでも 最新リリースにアップグレードができ 私のデータは永続化されます 今度の休暇の計画にSampleTrips アプリを使うのが待ちきれないです スキーママクロを利用して スキーマの追加メタデータを伝達し アプリケーションの進化に対応します バージョン管理スキーマを 使用することでアプリは以前の リリースから移行することができます 他の講演もご覧ください SwiftDataで作る素晴らしいアプリを 見るのが楽しみです とても光栄に感じてます
-
-
0:56 - Original Trip model
import SwiftUI import SwiftData @Model final class Trip { var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? }
-
1:50 - Adding a unique attribute
@Model final class Trip { @Attribute(.unique) var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? }
-
2:48 - Specifying original property names
@Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? }
-
4:00 - Cascading delete rule
@Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date @Relationship(.cascade) var bucketList: [BucketListItem]? = [] @Relationship(.cascade) var livingAccommodation: LivingAccommodation? }
-
4:54 - Transient properties
@Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date @Relationship(.cascade) var bucketList: [BucketListItem]? = [] @Relationship(.cascade) var livingAccommodation: LivingAccommodation? @Transient var tripViews: Int = 0 }
-
7:12 - Defining versioned schemas
enum SampleTripsSchemaV1: VersionedSchema { static var models: [any PersistentModel.Type] { [Trip.self, BucketListItem.self, LivingAccommodation.self] } @Model final class Trip { var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? } // Define the other models in this version... } enum SampleTripsSchemaV2: VersionedSchema { static var models: [any PersistentModel.Type] { [Trip.self, BucketListItem.self, LivingAccommodation.self] } @Model final class Trip { @Attribute(.unique) var name: String var destination: String var start_date: Date var end_date: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? } // Define the other models in this version... } enum SampleTripsSchemaV3: VersionedSchema { static var models: [any PersistentModel.Type] { [Trip.self, BucketListItem.self, LivingAccommodation.self] } @Model final class Trip { @Attribute(.unique) var name: String var destination: String @Attribute(originalName: "start_date") var startDate: Date @Attribute(originalName: "end_date") var endDate: Date var bucketList: [BucketListItem]? = [] var livingAccommodation: LivingAccommodation? } // Define the other models in this version... }
-
7:49 - Implementing a SchemaMigrationPlan
enum SampleTripsMigrationPlan: SchemaMigrationPlan { static var schemas: [any VersionedSchema.Type] { [SampleTripsSchemaV1.self, SampleTripsSchemaV2.self, SampleTripsSchemaV3.self] } static var stages: [MigrationStage] { [migrateV1toV2, migrateV2toV3] } static let migrateV1toV2 = MigrationStage.custom( fromVersion: SampleTripsSchemaV1.self, toVersion: SampleTripsSchemaV2.self, willMigrate: { context in let trips = try? context.fetch(FetchDescriptor<SampleTripsSchemaV1.Trip>()) // De-duplicate Trip instances here... try? context.save() }, didMigrate: nil ) static let migrateV2toV3 = MigrationStage.lightweight( fromVersion: SampleTripsSchemaV2.self, toVersion: SampleTripsSchemaV3.self ) }
-
8:40 - Configuring the migration plan
struct TripsApp: App { let container = ModelContainer( for: Trip.self, migrationPlan: SampleTripsMigrationPlan.self ) var body: some Scene { WindowGroup { ContentView() } .modelContainer(container) } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。