💯

WWDC 2024 での SwiftData:革命は続いているが、安定にはまだ時間が必要

2024/06/13に公開

SwiftData は昨年の初登場以来、開発者コミュニティの注目を集めているフレームワークです。WWDC 2024 が近づくにつれて、業界では SwiftData が機能、性能、安定性の面で画期的な進展を遂げることを広く期待しています。この記事では、SwiftData の最新バージョンのパフォーマンスを評価し、新しいバージョンを初めて体験した際に感じた様々な複雑な感情:驚き、喜び、落胆、混乱を共有します。

この原文は私のブログ Fatbobman's Blog に掲載されています。Swift、SwiftUI、Core Data、SwiftData に関する最新のアップデートや優れた記事をお見逃しなく。Fatbobman's Swift Weekly に登録して、毎週の洞察と貴重なコンテンツを直接メールボックスにお届けします。

データ管理フレームワークの革命は続いている

WWDC 2023 で、SwiftData は Apple エコシステムの次の十年にわたる最重要データ管理フレームワークとしての役割を担い、登場しました。初版がリリースされて以来、この Core Data の「後継者」は、現代のプログラミングコンセプトへの深い理解と潜在的な大きな力で、すべての人に強い印象を与えました。

Core Data の「後継者」と称される理由は二つあります。まず、長期的に Apple エコシステムにおける Core Data の役割を担うことになります。次に、初版の SwiftData は Core Data と高度に連携しており、開発者は SwiftData 内で多くの Core Data に対応するコンポーネントを見つけることができます。この連携に基づき、私は SwiftDataKit ライブラリを開発し、開発者が SwiftData のコンポーネントの Core Data 実装に直接アクセスできるようにしました。

記事 新しいフレームワーク、新しい思考:Observation と SwiftData フレームワークの解析 で、私は SwiftData のデザインコンセプトを高く評価しました。特に、データモデリングと並行操作における革新は、Core Data に対する革命であると言えます。

SwiftData チームは初版の革新後、安定性の改善と機能強化に焦点を当てると思われました。しかし、WWDC 2024 での SwiftData のアップデートは私の予想を完全に覆しました。なんと、基本的なデータストレージのロジックが書き換えられ、以前の Core Data との密接な結合が解除され、広範囲にわたる抽象化と分割が行われました。この変更には本当に驚きました。

WWDC 2024 での SwiftData は、Swift 言語の新機能をフルに活用し、効率的なデータモデリング能力、安全な並行操作メカニズム、簡潔な述語表現を備え、さまざまな基本データストレージタイプとの互換性を持つ現代のデータ管理フレームワークに進化しました。SwiftUI との連携もさらにスムーズになっています。

このバージョンから、SwiftData はもはや Core Data 上に構築されているとは言えません。私たちはただ、SwiftData がサポートするデフォルトのストレージ形式が Core Data と一致していると言うことができます。次のバージョンからは、データベースデータとの間の底層処理も Core Data のコードに依存しなくなる可能性があります。

現在のバージョンでは、-com.apple.CoreData.SQLDebugを使用することで Core Data の操作情報を観察することができます。

SwiftUI が 5 年間で 6 つのバージョンをリリースしたにもかかわらず、これほど深い基層の変更を経験したことは

ありません。それに比べて、SwiftData はわずか 2 年目にしてこのような大規模な改革を敢行する勇気は本当に称賛に値しますが、このような大胆な改革は新バージョンの安定性に対して懸念を抱かせます。

データストレージロジックの調整は長期的に見て極めて必要ですが、初版で行われなかったことは少し残念です。

ストレージロジックの調整により、SwiftDataKit は更新後の SwiftData にはもはや適用されません。

WWDC 2024 がもたらす新機能

一見すると新機能が少ないように見える今回のアップデートですが、実装方法と潜在的な大きな影響力には多くの驚きが含まれています。

カスタムデータストレージ

今回のアップデートで、SwiftData は大きな変革を遂げ、開発者が DataStoreDataStoreConfiguration プロトコルに準拠したカスタム実装を用いて、基底のストレージフォーマットを定義できるようになりました。この機能により、開発者は表層のコードを同じに保ちながら、ファイル、各種データベース、またはネットワークデータベースなど、多様なデータストレージ方式を底層で採用することが可能になります。

ModelConfiguration(即ちDataStoreConfiguration)を使ってModelContainerを構築する際、システムはデフォルトで Core Data ストレージフォーマットをサポートするDefaultStoreDataStore)実装を使用します。

この革命的な変更は、多くの SwiftData ユーザーの日常使用に大きな影響を与えるわけではありませんが、ほとんど感じられないコードの変更であっても、一定期間、安定性に問題を引き起こす可能性があります。

この機能についての詳細は Create a custom data store with SwiftData をご覧ください。将来の記事でこの機能についてさらに詳しく分析します。

データ変更履歴の追跡

SwiftData の初版は Core Data の 永続履歴追跡 機能のようなものを含んでおらず、willSaveおよびdidSave通知の不足がアプリ外での自動的なデータ変更監視の能力を制限していました。幸いなことに、この欠点は最新のアップデートで修正されました。

DataStoreプロトコルにはデータ変更履歴へのアクセスインターフェースが新たに追加され、modelContextも対応する API を提供しています。これらの新しい API と履歴データフォーマットは、より現代的で Swift 言語のスタイルに適合していますが、その操作ロジックと処理方式は Core Data の永続履歴追跡と非常に類似しています。

この機能についての詳細は Track model changes with SwiftData history をご覧ください。

データの一括削除

DataStoreプロトコルには現在、データの一括操作機能が含まれており、主に一括削除をサポートしています。SwiftData のDefaultStoreはこの API を実装しており、開発者がdelete<T>(model: T.Type, where predicate: Predicate<T>?, includeSubclasses: Bool)およびdeleteAllDataを呼び出す際に、基

底の一括操作ロジックを利用できます(高確率で)。

新しいアノテーション

  • #Unique マクロ:このマクロは一意性制約を定義するために使用されます。Attributeマクロのuniqueオプションと比較して、#Uniqueマクロは複数の属性にまたがる複合制約をサポートします。SwiftData のデフォルトのストレージ実装は SQLite の制約メカニズムに依存しているため、このマクロを使用するデータモデルは** CloudKit の同期ルールには適用されません**。
@Model
final class Person {
    // Declare any unique constraints as part of the model definition.
    #Unique<Person>([\.id], [\.givenName, \.familyName])

    var id: UUID
    var givenName: String
    var familyName: String

    init(id: UUID, givenName: String, familyName: String) {
        self.id = id
        self.givenName = givenName
        self.familyName = familyName
    }
}
  • #Index マクロ:このマクロは、一つまたは複数の属性にインデックスを作成することを可能にし、検索効率を向上させます。検索や並べ替えに頻繁に使用される属性に対して、インデックスを適用することで、クエリ速度が顕著に向上します。しかし、インデックスの使用は更なるストレージスペースを消費し、書き込み性能に影響を及ぼす可能性があります。そのため、クエリ条件や並べ替えの基準として頻繁に使用され、かつ大量のデータを扱う属性にインデックスを作成することで、実際の利益を得ることができます。
@Model 
class Trip {
    #Index<Trip>([\.name], [\.startDate], [\.endDate], [\.name, \.startDate, \.endDate])

    var name: String
    var destination: String
    var startDate: Date
    var endDate: Date
    
    var bucketList: [BucketListItem] = [BucketListItem
    var livingAccommodation: LivingAccommodation
}
  • preserveValueOnDeletion オプションAttributeマクロにpreserveValueOnDeletionオプションを追加することで、データが削除された場合でも、その属性の内容はデータ変更履歴の削除レコードに保持されます。これにより、開発者は履歴レコードの具体的な内容に基づいて適切な後処理を行うことができます。
@Model 
class Trip {
    @Attribute(.preserveValueOnDeletion)
    var startDate: Date

    @Attribute(.preserveValueOnDeletion)
    var endDate: Date
}

より使いやすいプレビュー環境の設定

WWDC 2024 のアップデートにより、SwiftData データを含むビューのプレビューがより便利で安全になりました。

  • PreviewTrait:SwiftUI の最新アップデートでは、開発者が簡単に PreviewTrait をカスタマイズできるようにする PreviewModifier プロトコルが導入されました。以下のコードは、PreviewModifierの実装を示しており、プレビュー用ビューにmodelContainerを作成し、デモデータを生成し、コンテキストインスタンスを注入します。
struct SampleData: PreviewModifier {
    static func makeSharedContext() throws -> ModelContainer {
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        let container = try ModelContainer(for: Trip.self, configurations: config)
        Trip.makeSampleTrips(in: container)
        return container
    }
    
    func body(content: Content, context: ModelContainer) -> some View {
        content.modelContainer(context)
    }
}

extension PreviewTrait where T == Preview.ViewTraits {
    @MainActor static var sampleData: Self = .modifier(SampleData())
}
  • @Previewable マクロ:このマクロは、開発者がプレビュー用のラッパービューを構築するプロセスを大幅に簡略化します。以下のサンプルコードは、プレビュービューのためのラッパービューを自動的に作成し、そのビュー内で@Queryを使用して関連データを取得する方法を示しています。modelContextおよびデモデータは、以前に定義されたPreviewTraitによって提供されます。
struct TripDetail: View {
    let trip: Trip?
    var body: some View {
        ...
    }
}

#Preview(traits: .sampleData) {
    @Previewable @Query var trips: [Trip]
    TripDetail(trip: trips.first)
}

複雑な述語の構築がさらに簡単に

WWDC 2024 で、Foundation の述語システムは多くの新機能を導入しました。新しい表現方法の追加はもちろんのこと、最も顕著な改善は#Expressionマクロの追加で、これにより述語表現の構築プロセスが大幅に簡素化されました。以前のバージョンでは、開発者は#Predicateマクロを使用する際にのみ構築の利便性を体験できました。今回、#Expressionマクロを通じて、独立した表現でも自然でスムーズな表現方法が実現できるようになりました。

#Expressionマクロを使用することで、開発者は複数の独立した表現を通じて述語を定義できます。これにより複雑な述語の構築がより明確になるだけでなく、表現の再利用性も向上します。

述語がブール値を返すのに対して、表現は任意の型を返すことができます。そのため、表現を宣言する際には、開発者が入力と出力の型を明確に指定する必要があります。

let unplannedItemsExpression = #Expression<[BucketListItem], Int> { items in
    items.filter {
        !$0.isInPlan
    }.count
}

let today = Date.now
let tripsWithUnplannedItems = #Predicate<Trip>{ trip
    // The current date falls within the trip
    (trip.startDate ..< trip.endDate).contains(today) &&

    // The trip has at least one BucketListItem
    // where 'isInPlan' is false
    unplannedItemsExpression.evaluate(trip.bucketList) > 0
}

尽管#Expressionマクロは非常に価値のあるツールですが、それを使用して 動的に複雑な述語を構築する 際には、まだいくつかの柔軟性に関する制約が存在します。さらに、拡張された表現機能があるとはいえ、SwiftData のDefaultStoreが述語を適切な SQL 命令に変換できるとは限りません。現バージョンが**オプショナルおよびto-many**を含む述語の変換問題を正しく解決しているかどうかについて、私はまだ詳細なテストを行っていません。関連情報があれば、X で私にお知らせください。

前バージョンの不足が解決されたかどうか

記事 WWDC 2024 前に書く:SwiftData の未来の可能性と現実の課題 で、私は SwiftData の初版で欠けていた重要な機能と主な問題を列挙しました。初期のテストを経て、少なくとも以下の問題と要求は依然として解決されていないことが判明しました:

  • ネットワーク同期は依然としてプライベートデータベースのみをサポートしています。
  • 非主要コンテキストで@ModalActorを使用してデータ操作を行うと、ビューがデータ変更に正しく反応しないことがあります(前バージョンよりも悪化している場合があります)。
  • 複数のリレーションを扱う際、複数端でのデータ挿入のパフォーマンス問題が依然として存在します。
  • didSaveおよびwillSaveの通知機能は依然として使用できません。

初版の安定性について評価が低かったため、もうしばらくしてからさらに深いテストを行う予定です。

正直なところ、現在の問題解決状況からすると、かなり落胆しています。

今、プロジェクトで SwiftData を使うべきか?

この数日間、多くの開発者から新しいプロジェクトで SwiftData を採用するべきかどうかについての質問を受けています。現時点では少々迷っています。

機能面では、いくつかの欠点はあるものの、更新後の SwiftData は大多数のアプリケーションシナリオに対応可能です。しかし、その安定性についてはまだ十分な自信が持てていません。特に今回の更新での底層構造の大規模な調整を考慮すると、短期的な安定性への影響はまだ予測が難しいです。

そのため、SwiftData をまだ使用していない開発者には、今後 1〜2 ヶ月の間、実際のプロジェクトでの SwiftData の導入を避けることをお勧めします。時間を置いてその安定性がさらに確認できた後に採用を検討してください。もちろん、その間に SwiftData のドキュメントや記事を深く理解し、その全く新しいデザインコンセプトを学ぶことは非常に重要です。

SwiftData が実際のアプリケーションでの信頼性を早く証明できることを願っています。

SwiftData、Apple が Swift コミュニティへ再び贡献するもの?

この瞬間、多くの読者が心を沈めているかもしれません。これほど優れたフレームワークであるにもかかわらず、なぜ安定性の最適化を最優先事項にしないのでしょうか?

この記事を書く過程で、この疑問が頭を離れませんでした:なぜ Apple はフレームワーク発表からわずか一年後にこれほど大きな調整を行うのでしょうか?その目的は何であり、どのように将来の展開と見通しに影響を与えるのでしょうか?

新しい API を深く学ぶことで、現在の SwiftData が Apple のエコシステムから大きく解離していることがわかりました。言い換えれば、それはすでにクロスプラットフォームのオープンソース Swift フレームワークになる基盤を初歩的に備えています。SwiftData がプラットフォームに依存しないデフォルトのストレージ実装をさらに提供できれば、今後数年間で非 Apple のエコシステムでその応用が見られる可能性が高く、Swift 言語のクロスプラットフォーム影響力を高める重要な手段となるでしょう。

これが、SwiftData が大規模な変革を続ける理由かもしれません。最終的にオープンソースとなるとき、それは新たな革命の始まりを示すことになります!その日が早く来ることを私たちは楽しみにしています。

Discussion