【SwiftData】ModelContextについて、公式ドキュメントを読む

# SwiftDataのModelContextについて、公式ドキュメントを読む

モデルに対してデータを取得、挿入、削除、そして、行われた変更をディスクに保存するオブジェクト。

> Swiftを基礎から学ぶには
自著、工学社より発売中の「[まるごと分かるSwiftプログラミング](https://amzn.asia/d/7YPWJwl)」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。
> また、Swiftプログラミングを基礎から動画で学びたい方には、Udemyコース「[今日からはじめるプログラミング](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwii1qTuj_2BAxV4hlYBHbj5BqEQFnoECBYQAQ&url=https%3A%2F%2Fwww.udemy.com%2Fcourse%2Fstart-programming-today%2F&usg=AOvVaw2vs4Sccq-bpfA4ZD2rCyNm&opi=89978449)」をお勧めします。

## 概要

モデルコンテキストはSwiftDataの中心概念であり、永続モデルのライフサイクル全体を管理します。
モデルコンテキストを使用して、新しいモデルを挿入し、それらのモデルへの変更を追跡して永続化し、不要になったらモデルデータは削除します。
モデルコンテキストはアプリのスキーマを理解しますが、永続ストレージからフェッチしたり、新しいモデルで入力するように指示するまでは、個々のモデルに関知しません。
コンテキストが暗黙的に永続ストレージに書き込むか、手動で`save()`を呼び出すまで、モデルに加えられた変更はメモリにのみ存在します。
暗黙の書き込みについて、詳細は[autosaveEnabled](https://developer.apple.com/documentation/swiftdata/modelcontext/autosaveenabled)を参照してください。

アプリのスキーマがモデル間の関係を明確にするなら、初期化時に各モデルをコンテキストに手動で挿入する必要はありません。
代わりに、関連するモデルのグラフを作成し、ルートモデルのみをコンテキストに挿入します。
コンテキストは階層を認識し、モデルの挿入を自動的に処理します。
グラフに新しいモデルと既存のモデルの両方が含まれている場合でも、同じ動作が適用されます。

モデルコンテキストは、アプリのスキーマと永続ストレージに関する理解をモデルコンテナに依存します。
アプリのウィンドウグループまたはビュー階層にコンテナをアタッチすると、関連するコンテキストがSwiftUI環境で利用できるようになります。
このコンテキストはメインアクターにバインドされており、フレームワークはモデルの変更を暗黙的に保存するようコンテキストを構成します。
`Query()`マクロは、フェッチを実行するために同じコンテキストを使用します。

```swift:
struct LastModifiedView: View {
    // ビュー環境からモデルコンテキストを取得
    @Environment(\.modelContext) private var modelContext

    ...
}
```

> 重要
モデルコンテナを明示的にアタッチしない場合、環境はインメモリのスキーマレスコンテナにバインドされたコンテキストを提供します。
このコンテキストにモデルを挿入しようとすると、フレームワークがエラーをスローし、実行したフェッチは空の結果を返します。

モデルコンテキストへのアクセスを確立したら、そのコンテキストの`insert(_:)`メソッドおよび`delete(_:)`メソッドを使用してモデルを追加および削除します。
`delete(model:where:includeSubclasses:)`メソッドを使用して、複数のモデルを一度に削除することもできます。
コンテキストは既知のモデルへのすべての変更を自動的に追跡します。
そのため、モデルを更新するためのメソッドはありません。
`hasChanges`プロパティを使用して「コンテキストに未保存の変更があるか」を判断します。
そして、`rollback()`メソッドを呼び出して、保留中の挿入と削除を破棄し、変更されたモデルを最新の保存状態に復元します。

`Query()`マクロはモデルをフェッチするものですが、モデルコンテキストを使用してほぼ同一のフェッチを実行できます。
例えば、`fetch(_:)`メソッドおよび`fetch(_:batchSize:)`メソッドを使用して、一連の基準に一致する型のすべてのモデルを取得します。
そして、`fetchCount(_:)`メソッドを使用して、モデル自体を取得するオーバーヘッドなしで、基準に一致するモデルがいくつあるかを特定します。
基準に一致するが、関連するデータを必要としないモデルを識別できるようにする必要がある場合は、`fetchIdentifiers(_:)`メソッドや`fetchIdentifiers(_:batchSize:)`メソッドを使用して、それらのモデルの永続的な識別子のみを取得します。

モデルコンテキストは、保存操作を試みる前に`willSave`通知をポストます。
そして、その操作が成功した直後には`didSave`通知をポストします。
アプリ側でこれらのイベントを認識するには、必要な通知を購読してください。
`didSave`通知は挿入、更新、および削除されたモデルに関する付加的な情報を提供します。

```swift:
struct LastModifiedView: View {
    @Environment(\.modelContext) private var context
    @State private var lastModified = Date.now
    
    private var didSavePublisher: NotificationCenter.Publisher {
        NotificationCenter.default
            .publisher(for: ModelContext.willSave, object: context)
    }
    
    var body: some View {
        Text(lastModified.formatted(date: .abbreviated, time: .shortened))
            .onReceive(didSavePublisher) { _ in
                lastModified = Date.now
            }
    }
}
```

> メモ
不要な通知や意図しない通知の受信を避けるには、パブリッシャーを作成する際に必ず、オブジェクトパラメータとしてモデルコンテキストを指定してください。

## 参考

Apple Developer Documentation

- [Preserving your app’s model data across launches](https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches)
- [ModelContext](https://developer.apple.com/documentation/swiftdata/modelcontext)