💡

SwiftDataの公式ドキュメントを読み込む その1

2024/02/18に公開

SwiftDataについて

WWDC2023で発表されたばかりのAPIである。iOS17からしか実装できない点を注意しよう。
しかし、このSwiftDataは従来のCoreDataやCloudkitの煩雑さをとっぱらってくれているらしい。
とはいえ、勉強しないと使えないのはどのAPIも同じ。読んでいこう。
公式ドキュメントはここ:https://developer.apple.com/documentation/swiftdata

アプリのモデルを起動中に保存する

この項目
https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches

クラスを永続的なモデルに変換する

The framework provides tools such as macros and property wrappers that enable you to expressively describe your app’s schema in Swift code, removing any reliance on external dependencies such as model and migration mapping files.

この文言に書いてあるように、コードのみで他の設定ファイルはデータモデルを形成するためには必要ないということである。
いままではCoreDataだとxcadatamodelファイルが必要で、XcodeでEntityとかを設定する必要でしたね。それがいらんということで。
最近のAppleの傾向として、過去やってきたApple製品ソフトに必要な独自のツールを排除する傾向にあります。コードファーストにしたいということですね。

それでモデルを作るにはどうしたらいいかというと@Modelマクロを使えということらしいです。これも最近のSwiftらしい傾向。とにかく必要だけどめんどいコードはマクロにまとめてしまえと。
以下のサンプルが書いてあります。

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?
}

でもこの@Modelはなにをしてくれるかというと、このクラスをPersistentModelプロトコルに適用してくれるらしいのです。
PersistentModelの役割は以下の通り

  • クラスを調べて内部のスキーマを生成してくれる。

これだけ言われてもなあと思いますが、このクラスの定義通りに永続的にデータを保存できて、データを操作できるオブジェクトを生成してくれるということだと思います。ここでいうとTripという便利なオブジェクトが出来上がるということでしょう。
さらに@ModelオブジェクトはObserableプロトコルにも同時に適用してくれるので、SwiftUIで使用する場合には、アプリにデータの変更を即座に反映できるとのこと。
クラスのプロパティはCodableプロトコルと互換性のあるタイプであれば使える。

モデルの属性(attribute)の永続的な振る舞いをカスタマイズする

An attribute is a property of a model class that SwiftData manages.

「attributeとはSwiftDataが管理するモデルクラスのプロパティである」と書いてある。要は上のTripクラスのnameとかdestinationとかに当たる。クラスのプロパティそのものであるが、@Modelをつけた場合はattributeと呼んでほしいらしい。

In most cases, the framework’s default behavior for attributes is sufficient. However, if you need to alter how SwiftData handles the persistence of a particular attribute, use one of the provided schema macros.

大体の場合はデフォルトでもOK。だけど、SwiftDataがattributeをどうあつかうかを変えたいばあいもあるでしょ?と言っている

For example, you may want to avoid conflicts in your model data by specifying that an attribute’s value is unique across all instances of that model.

例えば、モデルのすべてのインスタンスの中で、attributeの値をユニークにすることでコンフリクトを避けたい場合があるらしい。
どういう場合だ?すまん、今の僕ではここはよくわからん。いずれなんか遭遇するんだろうか。
それともSQLのUNIQUEと同じなのだろうか。
ちなみに以下のようにすれば設定できる

@Attribute(.unique) var name: String

unique以外にもhttps://developer.apple.com/documentation/swiftdata/schema/attribute/option
に書かれているプロパティで振る舞いを変えられるらしい。

SwiftData implicitly manages the relationship between those models for you. By default, the framework sets relationship attributes to nil after you delete a related model instance.

リレーションは自動で管理できる。しかし、データ消去に関するルールを変えたい場合は@Relationshipマクロを使うらしい。

@Relationship(.cascade) var accommodation: Accommodation?

この例ではカスケード削除をaccomodation attributeに適用している。これもまだ使わないかな。

SwiftData persists all noncomputed attributes of a model by default, but you may not always want this to happen. For example, one or more properties on a class may only ever contain temporary data that doesn’t need saving, such as the current weather at an upcoming trip’s destination.

データを永続的に保存したくない場合は@Transientを使えばいい。

モデルストレージを管理する

inメモリにしたい場合やらCloudkitコンテナを特定する場合に、configurationを指定する必要がある。
まずデフォルトのストレージを管理する場合、modelContainerモディファイアーを使う。ここがSwiftUIらしいところ。これは以下のように、viewのトップの層に適用する。
使用するモデルを配列で指定する。

import SwiftUI
import SwiftData


@main
struct TripsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .modelContainer(for: [
                    Trip.self,
                    Accommodation.self
                ])
        }
    }
}

SwiftUIをつかなくてもいい方法がある。SwiftUIをつかなくてもいい方法は結構いろいろな機能に用意されている。
ここで指定されたTripとAccommodationはプロジェクト内でSwiftDataもモデルとして使えるようになる。

プロジェクトで使用するためにモデルを保存する

model contextというものをモデルのインスタンスをつかうために生成する。

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) private var context
}

保存する時は以下のように明示的にinsert()などを呼び出す必要がある。

var trip = Trip(name: name, 
                destination: destination, 
                startDate: startDate, 
                endDate: endDate)


context.insert(trip)

もっと詳しいことを知りたければ以下のリンクを見ればいいらしい。
https://developer.apple.com/documentation/swiftdata/modelcontext

知らなきゃいけないことがたくさんだぁ・・・。

表示したり、加工するためにデータを取り出す

SwiftData provides the Query property wrapper and the FetchDescriptor type for performing fetches.

表示するにはQeuryプロパティラッパーとFetchDescriptorが利用できるらしい。
モデルを配列にして変数に格納すれば簡単に一覧表示ができる。

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)
            }
        }
    }
}

View外ではFetchDescriptorを使うらしい。

ああ・・・疲れた・・・

Discussion