【WWDC23】SwiftDataの詳細設定
はじめに
SwiftDataを活用してアプリケーションを構築する方法について説明します。
- データの永続化の設定
- ModelContainerの設定
- 条件を指定したクエリ機能
の順に説明します。
永続化の設定
モデルの定義
SwiftDataでは、Swiftですでに存在するstruct・classを用いて活用できます。データを永続化するための基本単位としてModelマクロを使用します。以下は、SampleTripsアプリケーションのTripクラスの定義例です。
// @Modelマクロを使って、SwiftDataに永続化するクラスであることを示します
@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?
}
@Relationship()を用いることによって、他にモデル定義との関係性を定義できます。
ModelContainerの初期化

スキーマはModelContainerと呼ばれるクラスに適応され、データがどのように永続化するかを記述します。ModelContainerはスキーマを読み取って、Modelクラスのインスタンスを保持できるデータベースを生成します。

コード内でModelクラスのインスタンスを扱う場合は、それらのインスタンスはメモリ内の状態を追跡し、管理するModelContextにリンクされます。
以下の例では、Tripクラスだけを使用してModelContainerを初期化しています。
// Tripクラスを使ってModelContainerを初期化
let container = try ModelContainer(for: Trip.self)
上記の実装のみで問題はないです。その他の関連したスキーマ(BucketListItem,LivingAccommodation)も推測して自動で定義されます。
ModelConfigurationの使用
ModelConfigurationを使用してModelContainerをカスタマイズできます。例えば、データの保存先(メモリorディスクなど)を制御するために、以下のように複数のスキーマとファイルURLを指定してModelContainerを作成できます。
// 全体のスキーマを定義
let fullSchema = Schema([
Trip.self,
BucketListItem.self,
LivingAccommodations.self,
Person.self,
Address.self
])
// Trip関連のデータの永続化設定
let trips = ModelConfiguration(
schema: Schema([
Trip.self,
BucketListItem.self,
LivingAccommodations.self
]),
url: URL(filePath: "/path/to/trip.store"),
cloudKitContainerIdentifier: "com.example.trips"
)
// Person関連のデータの永続化設定
let people = ModelConfiguration(
schema: Schema([Person.self, Address.self]),
url: URL(filePath: "/path/to/people.store"),
cloudKitContainerIdentifier: "com.example.people"
)
// 複数の設定を使ってModelContainerを初期化
let container = try ModelContainer(for: fullSchema, trips, people)
他にも、読み込み制限・ローカルファイルとの連携などを簡単に設定できます。
SwiftUIでのModelContainerの使用
SwiftUIでは、modelContainerモディファイアを使用してModelContainerを簡単に作成できます。
@main
struct TripsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
// modelContainerモディファイアを使用してModelContainerを作成
.modelContainer(for: Trip.self)
}
}
ModelContextの参照
SwiftUIのビューでModelContextを参照し、CRUD処理を簡単に実装可能です。
struct ContentView: View {
@Query var trips: [Trip]
// ModelContextを取得
@Environment(\.modelContext) var modelContext
var body: some View {
NavigationStack {
List {
// 旅行リストを表示
ForEach(trips) { trip in
TripListItem(trip: trip)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
// modelContextから旅行を削除
modelContext.delete(trip)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
.onDelete(perform: deleteTrips(at:))
}
}
}
}
modelContainer(for:inMemory:isAutosaveEnabled:isUndoEnabled:onSetup:)モディファイアが存在しパラメーターを設定することにより、柔軟なデータ管理が可能となります。
| 項目名 | 説明 |
|---|---|
| modelType | スキーマを定義するモデルタイプ |
| inMemory | データをメモリ内のみに保存するかどうか |
| isAutosaveEnabled | オートセーブが有効かどうか |
| isUndoEnabled | 元に戻す操作を管理するためのundoManagerを使用するかどうか |
| onSetup | コンテナの作成が成功または失敗したときに呼び出されるコールバック |
-
isUndoEnabledをtrueに設定することによって、追加コード無しで変更を取り消しできます。3本指でスワイプorシェイクなどのジェスチャーで可能です。デフォルトはfalseです。 -
isAutosaveEnabledはをtrueに設定されており、フォアグラウンド・バックグラウンドへ移動などのイベントに応じて自動でContextが保存されます。デフォルトはtrueです。
条件を指定したクエリ機能
Predicateマクロを使ったクエリ
PredicateはSwiftの構文でクエリを記入できます。
let context = self.newSwiftContext(from: Trip.self)
let hotelNames = ["First", "Second", "Third"]
// Predicateマクロを使ってフィルタ条件を定義
var predicate = #Predicate<Trip> { trip in
trip.livingAccommodations.filter {
hotelNames.contains($0.placeName)
}.count > 0
}
FetchDescriptorにPredicateの条件
やSortDescriptorを指定することによって、取得するモデルの指定や並び替えが可能です。
// FetchDescriptorにPredicateを設定してクエリを実行
var descriptor = FetchDescriptor(predicate: predicate)
var trips = try context.fetch(descriptor)
enumerate(_:batchSize:allowEscapingMutations:block:)を用いて、取得データの列挙もできます。
// FetchDescriptorを使用してオブジェクトを列挙
context.enumerate(FetchDescriptor<Trip>()) { trip in
// tripオブジェクトに対して操作を行う
}
enumerate(_:batchSize:allowEscapingMutations:block:)を用いると
- バッチサイズ(デフォルトは5000)
- allowEscapingMutations(データ取得中にデータ変更が許可されるかどうか:デフォルトはfalse)
を指定できます。
context.enumerate(
descriptor,
batchSize: 500,
allowEscapingMutations: true
) { trip in
}
参考資料
最後に
間違い・気になる部分がありましたら、コメントいただけると大変うれしいです。
良かったと思ったら記事へのいいね、Xのフォローをよろしくお願いいたします。
個人でアプリを作成しています。良かったら覗いてみてください。
Discussion