💾

【WWDC23】SwiftDataの詳細設定

2024/06/28に公開

はじめに

SwiftDataを活用してアプリケーションを構築する方法について説明します。

  • データの永続化の設定
  • ModelContainerの設定
  • 条件を指定したクエリ機能

の順に説明します。

永続化の設定

モデルの定義

SwiftDataでは、Swiftですでに存在するstructclassを用いて活用できます。データを永続化するための基本単位として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
}

FetchDescriptorPredicateの条件
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
    
}

参考資料

https://developer.apple.com/videos/play/wwdc2023/10196/

最後に

間違い・気になる部分がありましたら、コメントいただけると大変うれしいです。
良かったと思ったら記事へのいいねXのフォローをよろしくお願いいたします。

https://sites.google.com/view/muranakar

個人でアプリを作成しています。良かったら覗いてみてください。

Discussion