jack blog
作者一覧
サイト移転のお知らせ
jack-webは下記のアドレスに移動しました
2023-12-11

SwiftDataのテンプレートを読む

syosyo

はじめに

こんにちは アドベントカレンダー11日目担当のsyoです。 特にこだわりがあるわけではないのですが結果的にApple製品を多く持っているので、Appleが作成したプログラミング言語であるSwiftに興味があり、WWDC2023でSwiftDataなるものが発表されたのでそれについて調べてみました。

SwiftDataとはデータ永続化の新しいフレームワーク

まずは公式の引用

SwiftDataは、アプリ内のデータを管理するためのまったく新しいフレームワークです。通常のSwiftコードを使ってモデルを記述でき、カスタムエディタは必要ありません。SwiftDataでは、関係管理、元に戻す/やり直しのサポート、iCloudとの同期などが自動的に行われます。また、SwiftDataはSwiftUIと統合されているため、データがすぐに反映され、ビューは常に最新の状態に保たれます。

引用の繰り返しになりますが、

  1. Swiftコードでデータモデルの記述ができる
  2. SwiftUIと統合され、簡単にデータの反映ができる

がポイントだと思います。

SwiftDataのテンプレートコードを読んでいく

実行の様子

まずはイメージとして実行の様子を見てみましょう。 テンプレートコードでは、ボタンを押した瞬間のタイムスタンプのデータを管理します。タイムスタンプのデータをSwiftDataで永続化して管理しており、追加と削除が可能です。

  1. 右上の「+」を押すと、ボタンを押したタイミングのタイムスタンプが追加されます。
  1. スワイプや「Edit」を押すことでタイムスタンプの削除ができます。

ソースコード

次にソースコードを見てみましょう。このコードはStorageにSwiftDataを選択して新規プロジェクトを作成すると生成されます。(方法は に書きました。)また、この節の下部に全てのコードを載せたので、必要に応じて見てみてください。

まずは初めに挙げた二つのポイント

  1. Swiftコードでデータモデルの記述ができる
  2. SwiftUIと統合され、簡単にデータの反映ができる

についてみていきます。

1. Swiftコードでデータモデルの記述ができる

import Foundation
import SwiftData

@Model //@Modelをつけることによってclassをスキーマとして扱うことができる
final class Item {
    var timestamp: Date
    
    init(timestamp: Date) {
        self.timestamp = timestamp
    }
}

ここではclassとしてItemが宣言され、そのItemクラスがプロパティにtimestampを持っています。普通のclassに@Modelをつけるだけでデータモデルの定義ができます。簡潔でわかりやすいですね。

2. SwiftUIと統合され、簡単にデータの反映ができる

import SwiftUI
import SwiftData

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

		//注目箇所
    @Query private var items: [Item]

    var body: some View {
        NavigationSplitView {
            List {

								//注目箇所
                ForEach(items) { item in
                    NavigationLink {
                        Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
                    } label: {
                        Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
                    }
                }
                .onDelete(perform: deleteItems)
            }
//省略

ここで注目して欲しいのは@Query private var items: [Item]の宣言部分とForEach(items) { item inから始まる利用部分です。この宣言をするだけでItemテーブル(?)のデータをitemsに格納してくれ、それを通常通りに利用して表示することができます。また、このコードのままでデータの更新があった際も表示に反映してくれます。

次に、データの追加・消去についてみていきます。

import SwiftUI
import SwiftData

struct ContentView: View {

		//注目箇所
    @Environment(\.modelContext) private var modelContext
    @Query private var items: [Item]

    var body: some View {
        //省略
    }

    private func addItem() {
        withAnimation {
            let newItem = Item(timestamp: Date())
						//注目箇所
            modelContext.insert(newItem)
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            for index in offsets {
								//注目箇所
                modelContext.delete(items[index])
            }
        }
    }
}

#Preview {
    ContentView()
				//注目箇所
        .modelContainer(for: Item.self, inMemory: true)
}

まず初めに@Environment(\.modelContext) private var modelContextと宣言がされています。modelContextはCRUD処理を担うobjectです。

このmodelContextを用いてmodelContext.insert(newItem)modelContext.delete(items[index])という関数を用いてデータの追加と削除が行われています。

初めの宣言の@Environmentは環境変数(?)を取得する際に利用するもので、タイムゾーンなどの取得ができます。このmodelContextはどこからくるんだというと、どうやら最後の.modelContainer(for: Item.self, inMemory: true)の時に設定されているようです。(https://developer.apple.com/documentation/SwiftUI/View/modelContainer(_:)を参照)これはPreviewようの部分ですが、ビルドした時場合に実行される部分でも多少表記が違いますが、同様のコードが見られます。

import SwiftUI
import SwiftData

@main
struct SwiftDataTestApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            Item.self,
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(sharedModelContainer)
    }
}

全てのコード

最後に全てのコードを載せておきます。

SwiftDataに関わるキーワード

ここまでで一応テンプレートのコードにざっと目を通したのですが、まだまだ分からないことだらけです。かといって長々と書いても読みにくい(そもそも書けない)ので必要になるであろうキーワードを書くことにします。

実はSwiftDataに登場している者たち(すみません、よく分かってないです。)

データモデルの定義関係

CRUD処理関係

SwiftDataで何かしようと思った場合に出てくる単語だと思うので、公式ドキュメントや先人たちを頼りに調べてみてください。

まとめ

Swiftはちょっと知らない間に大きく変わっていたりするのでちゃんと触れていないと分からなくなってしまうところがあります…

今回SwiftDataという新しいフレームワークに触れてみましたが、簡潔さの中にはマクロをはじめとして、別の新しい概念や隠されている部分も多くあり、これが果たして使いやすいものであるのかどうかはまだまだ分かりません。

個人的にはSwiftのコードでデータモデルを定義できるのは見やすくて良いなと感じたので、もう少し調べていきたいと思います。

参考

正直なところまだまだ情報が少なく、かつ私自身Swiftの知識も全然足りていません。先駆者の記事に大変お世話になりました。

おまけ SwiftDataの新規プロジェクトの作成

  1. Xcodeを開き、「Create New Project…」を選択
  2. 今回は「Multiplatform>App」を選択
  3. 「Product Name」を入力し、「Storage」で「SwiftData」を選択
  4. 完成(右側のPreviewはiPhoneにしました。)

おすすめ記事