この投稿は何?
Apple開発者向けドキュメント「Preserving your app’s model data across launches」を読む。
Swiftマクロを使用して、クラスをSwiftDataモデルに変換します。
そして、アプリを再起動してもモデルのインスタンスが消えないようにします。
Swiftを基礎から学ぶには
自著、工学社より発売中の「まるごと分かるSwiftプログラミング」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。
また、Swiftプログラミングを基礎から動画で学びたい方には、Udemyコース「今日からはじめるプログラミング」をお勧めします。
Overview
ほとんどのアプリは、作成または消費するデータをモデル化するいくつかの独自型を定義しています。
例えば、旅行アプリでは旅行、フライト、および予約された宿泊施設のクラスを定義かもしれません。
SwiftDataフレームワークはデータを迅速かつ効率的に保有して、アプリの起動を跨いで利用できるようにします。
そして、SwiftDataとSwiftUIを統合して、データを再フェッチして画面に表示できます。
SwiftDataフレームワークは、既存のモデルクラスを補完するように設計されています。
このSwiftDataは、Swiftコードでアプリのスキーマを表現的に記述できるマクロやプロパティラッパーなどのツールを提供します。
それによって、モデルや移行マッピングファイルなどの外部依存関係への依存を排除します。
Turn classes into models to make them persistable
SwiftDataでモデルクラスのインスタンスを保存するには、フレームワークをインポートし、Model()
マクロでそのクラスにアノテーションをマークします。
Model
マクロはクラスを更新して、PersistentModel
プロトコルに準拠させます。
PersistentModel
は「SwiftDataがクラスを解析して、内部スキーマを生成する」ためのプロトコルです。
さらに、マクロはObservabel
プロトコルの適合性も追加するので、クラスでの変更が追跡可能になります。
import SwiftData
// Annotate new or existing model classes with the @Model macro.
@Model
class Trip {
var name: String
var destination: String
var startDate: Date
var endDate: Date
var accommodation: Accommodation?
}
デフォルトでSwiftDataは計算プロパティを除く、ほとのどのプロパティをサポートします。
これにはBool
、Int
、String
などのプリミティブ型と、構造体、列挙型、および「Codable
プロトコルに準拠したその他の値型オブジェクト」などの複雑な型が含まれます。
モデルクラスを定義するコードは、アプリのモデルレイヤーにおける「信頼できる情報源」として機能し、それによってSwiftDataは永続データの一貫性を維持します。
Customize the persistence behavior of model attributes
属性は、SwiftDataが管理するモデルクラスのプロパティです。
ほとんどの場合、属性に対するフレームワークのデフォルトの動作で十分です。
ただし、SwiftDataが特定の属性について永続性をカスタマイズしたい場合は、スキーマのマクロを使用してください。
例えば、属性の値が「そのモデルのすべてのインスタンスで一意である」ことを指定することで、モデルデータの競合を回避したい場合があります。
属性の動作をカスタマイズするには、Attribute(_:originalName:hashModifier:)
マクロでプロパティにアノテーションをマークし、目的の動作を駆動するオプションの値を指定します。
@Attribute(.unique) var name: String
固有の制約を強制する以外に、@Attribute
は、削除された値の保存、Spotlightのインデックス作成、暗号化などをサポートしています。
基礎となるモデルデータに元の名前を保持したい場合は、@Attribute
マクロを使用して名前が変更された属性を正しく処理することもできます。
モデルに、型がモデル(またはモデルのコレクション)でもある属性が含まれている場合、SwiftDataはそれらのモデル間の関係を暗黙的に管理します。
デフォルトでは、フレームワークは関連するモデルインスタンスを削除した後、関係属性をnil
に設定します。
別の削除ルールを指定するには、Relationship(_:deleteRule:minimumModelCount:maximumModelCount:originalName:inverse:hashModifier:)
マクロでプロパティにアノテーションをマークします。
例えば、旅行を削除するときに、関連する宿泊施設を削除したい場合があります。
@Relationship(.cascade) var accommodation: Accommodation?
SwiftDataは、デフォルトでモデルのすべての非計算属性を保持しますが、必ずしもこれが起こるとは限りません。
例えば、クラスのプロパティには「今後の旅行の目的地の現在の天気」など、保存する必要のない一時的なデータしか含まれていないこともあります。
このようなシナリオでは、Transient()
マクロでこれらのプロパティにアノテーションをマークすると、SwiftDataはその値をディスクに書き込みません。
@Transient var destinationWeather = Weather.current()
Configure the model storage
SwiftDataがモデルを調べて必要なスキーマを生成する前に、実行時にどのモデルを永続させるか、およびオプションで基礎となるストレージに使用する構成を伝える必要があります。
例えば、テストを実行するときにストレージをメモリにのみ存在させたり、デバイス間でモデルデータを同期するときに特定のCloudKitコンテナを使用したりできます。
デフォルトのストレージを設定するには、ビュー(またはシーン)のmodelContainer(for:inMemory:isAutosaveEnabled:isUndoEnabled:onSetup:)
モディファイアに「永続化するモデル型の配列」を指定します。
このモディファイアをビューで使用する場合は、すべてのビュー階層が適切に環境を継承するように「ビュー階層のトップ」で適用してください。
import SwiftUI
import SwiftData
@main
struct TripsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(for: [
Trip.self,
Accommodation.self
])
}
}
}
SwiftUIを使用していない場合は、適切なイニシャライザを使用して手動でモデルコンテナを作成します。
import SwiftData
let container = try ModelContainer([
Trip.self,
Accommodation.self
])
Tip
モデル型にリレーションが含まれている場合は、配列からデスティネーションのモデル型を省略できます。
SwiftDataは自動的にモデルのリレーションを横断し、任意のデスティネーションのモデル型を含めます。
ModelConfiguration
を使用して、独自のストレージを作成することも可能です。
この型は以下の設定事項を含む、いくつかのオプションを提供します。
- ストレージをメモリ内だけの存在にする
- ストレージを読み取り専用にする
- アプリは特定のアプリグループを使用して、モデルデータを保存する
let configuration = ModelConfiguration(isStoredInMemoryOnly: true, allowsSave: false)
let container = try ModelContainer(
for: Trip.self, Accommodation.self,
configurations: configuration
)
Important
iCloudの自動同期はCloudKitエンタイトルメントの存在に依存し、SwiftDataはそのエンタイトルメントで見つかった最初のコンテナを使用します。
アプリに特定のコンテナが必要な場合は、そのコンテナをModelConfiguration
型インスタンスで指定します。
Save models for later use
実行時にモデルクラスのインスタンスを管理するには、モデルコンテキスト を使用します。
これは、「メモリ上にあるデータとモデルコンテナ間の調整役」を担うオブジェクトであり、データを永続化するものです。
「メインアクターにバインドされているモデルコンテナ」のコンテキストを取得するには、modelContext
環境変数を使用します。
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var context // 環境変数のコンテキストを取得
}
ビューの外、またはSwiftUIを使用していない場合は、モデルコンテナを使用して同じアクターバウンドコンテキストに直接アクセスします。
let context = container.mainContext
どちらの場合も、返されたコンテキストは「保存していない変更が残っているか」を定期的にチェックします。
未保存の変更がある場合は、それらの暗黙的な保存を代行します。
手動で作成するコンテキストでは、autosaveEnabled
プロパティをtrue
にして、同じ動作を取得します。
SwiftDataがモデルインスタンスを永続化し、その変更の追跡を開始するには、インスタンスをコンテキストに挿入します。
var trip = Trip(name: name,
destination: destination,
startDate: startDate,
endDate: endDate)
context.insert(trip)
挿入に続いて、コンテキストのsave()
メソッドを呼び出してすぐに保存するか、代わりにコンテキストで暗黙的に保存できます。
コンテキストは「既知のモデルインスタンスへの変更」を自動的に追跡し、それらの変更を後続のセーブに含めます。
保存に加えて、コンテキストを使用してモデルインスタンスをフェッチ、列挙、削除できます。
Fetch models for display or additional processing
モデルデータの永続化した後は、そのデータを取得し、モデルインスタンスとして具体化し、それらのインスタンスをビューに表示したり、その他のアクションを実行したりします。
SwiftDataは、フェッチを実行するためのQuery
プロパティラッパーとFetchDescriptor
型を提供します。
モデルインスタンスを取得したり、検索基準や優先ソート順を任意で適用するには、SwiftUIビューで@Query
を使用します。
@Model
マクロはモデルクラスにObservable
プロトコルの適合性を追加するので、SwiftUIは取得したインスタンスに変更が発生するたびにビューを更新できます。
import SwiftUI
import SwiftData
struct ContentView: View {
@Query(sort: \.startDate, order: .reverse) var allTrips: [Trip]
var body: some View {
List {
ForEach(allTrips) {
TripView(for: $0)
}
}
}
}
ビューの外側か、SwiftUIを使用していない場合は、ModelContext
のいずれかのフェッチメソッドを使用してください。
各メソッドは、述語とソート順序を含むFetchDescriptor
型インスタンスを期待します。
フェッチ記述子はバッチ処理、オフセット、プリフェッチなどに影響を与える設定を追加できます。
let context = container.mainContext
let upcomingTrips = FetchDescriptor<Trip>(
predicate: #Predicate { $0.startDate > Date.now },
sortBy: [
.init(\.startDate)
]
)
upcomingTrips.fetchLimit = 50
upcomingTrips.includePendingChanges = true
let results = context.fetch(upcomingTrips)
参考
Apple Developer Documentation