【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