WWDC Quick Look 💓 By SwiftGGTeam
Model your schema with SwiftData

Model your schema with SwiftData

元の動画を見る

ハイライト

SwiftData パス@Model@Attribute@Relationshipマクロを使用すると、データ モデリングが完全にコードベースになります。開発者は、外部ファイル形式を使用せずに、Swift コードを使用してデータベース スキーマを直接定義し、永続化動作を構成し、バージョン移行を管理できます。

主要内容

Core Data から SwiftData へ: モデリングアプローチの変化

以前は、モデリングに Core Data が使用されており、それを開く必要がありました。.xcdatamodeldファイルを作成し、エンティティを追加し、属性を構成し、グラフィカル インターフェイスで関係を設定します。モデルとコードは分離されており、一方の部分を変更するにはもう一方の部分を同期する必要があるため、エラーが発生しやすくなります。

SwiftData はモデリングを完全にコードに組み込みます。 Swift クラスに追加する@Modelマクロを使用すると、永続化機能が自動的に取得されます。

00:56@ModelホンランTripクラスは自動的に続きますPersistentModelプロトコル、フレームワークは、属性、定義に基づいて完全なスキーマを生成します。コードは真実であり、モデル定義はスキーマの唯一のソースです。

@Attribute: 属性の動作を細かく制御する

(01:50) アプリケーションを公開した後、同じ名前の Trip が競合を引き起こしていることが判明しました。@Attribute(.unique)これにより、属性値が一意であることを確認し、保存時に更新/挿入セマンティクスを実行できます。したいstart_dateに変更しますstartDateただし、名前を直接変更すると新しい属性とみなされ、データが失われます。@AttributeoriginalNameパラメータは古い属性名と新しい属性名をマッピングして、データ損失を回避し、スムーズな移行を実現できます。

詳細

@属性: 一意の制約と更新/挿入

(01:50) アプリケーションを公開した後、同じ名前の Trip が競合を引き起こしていることが判明しました。使用@Attribute(.unique)属性値が一意であることを確認できます。

@Model
final class Trip {
    @Attribute(.unique) var name: String
    var destination: String
    var start_date: Date
    var end_date: Date
    
    var bucketList: [BucketListItem]? = []
    var livingAccommodation: LivingAccommodation?
}

重要なポイント: -@Attribute(.unique)確認するnameフィールド値は一意です

  • 保存時に同じ名前のトリップが存在する場合は、upsert を実行します (最初に挿入を試み、競合がある場合は更新します)
  • 一意の制約は、基本タイプ (数値、文字列、UUID) と 1 対 1 の関係に適用されます。

@Attribute: 属性の名前変更マッピング

(02:48) したいですstart_dateに変更しますstartDateただし、名前を直接変更すると新しい属性とみなされ、データが失われます。使用originalNameパラメータのマッピング:

@Model
final class Trip {
    @Attribute(.unique) var name: String
    var destination: String
    @Attribute(originalName: "start_date") var startDate: Date
    @Attribute(originalName: "end_date") var endDate: Date
    
    var bucketList: [BucketListItem]? = []
    var livingAccommodation: LivingAccommodation?
}

重要なポイント: -originalName新しい属性名をデータベース内の古い列名にマッピングする

  • データ損失を回避し、スムーズな移行を実現します -@Attributeもサポート.externalStorage(外部ファイルに保存されたビッグデータ)および変換可能なタイプ

@Relationship: オブジェクトの関係を管理する

カスケード削除

(04:00) デフォルトの削除ルールは nullify です。Trip が削除されると、関連する BucketList と LivingAccommodation の外部キーが nil に設定され、孤立データになります。旅行を削除する場合は、関連するデータも削除します。

@Model
final class Trip {
    @Attribute(.unique) var name: String
    var destination: String
    @Attribute(originalName: "start_date") var startDate: Date
    @Attribute(originalName: "end_date") var endDate: Date
    
    @Relationship(.cascade)
    var bucketList: [BucketListItem]? = []
  
    @Relationship(.cascade)
    var livingAccommodation: LivingAccommodation?
}

重要なポイント: -@Relationship(.cascade)親オブジェクトを削除すると、すべての子オブジェクトも自動的に削除されます。

  • 暗黙的な逆関係が自動的に検出されるため、追加の注釈は必要ありません -@RelationshipもサポートoriginalName対多関係の最小/最大数の設定

@Transient: 永続性を必要としない属性を除外します。

(04:54) 旅行が閲覧された回数を記録したいとしますが、永続化する必要はありません。

@Model
final class Trip {
    @Attribute(.unique) var name: String
    var destination: String
    @Attribute(originalName: "start_date") var startDate: Date
    @Attribute(originalName: "end_date") var endDate: Date
    
    @Relationship(.cascade)
    var bucketList: [BucketListItem]? = []
  
    @Relationship(.cascade)
    var livingAccommodation: LivingAccommodation?

    @Transient
    var tripViews: Int = 0
}

重要なポイント: -@Transientマークされた属性はデータベースに保存されません

  • SwiftData からオブジェクトを取得するときに適切な初期値を確保するには、デフォルト値を指定する必要があります
  • キャッシュ、一時的な計算結果、および永続性を必要としないその他のデータに適しています。

スキーマの移行: バージョンを進化させる正しい方法

(07:12) アプリケーションの複数のバージョンがリリースされた後、スキーマを進化させる必要があります。 SwiftData が使用するものVersionedSchemaさまざまなバージョンのスキーマを定義します。

enum SampleTripsSchemaV1: VersionedSchema {
    static var models: [any PersistentModel.Type] {
        [Trip.self, BucketListItem.self, LivingAccommodation.self]
    }

    @Model
    final class Trip {
        var name: String
        var destination: String
        var start_date: Date
        var end_date: Date
    
        var bucketList: [BucketListItem]? = []
        var livingAccommodation: LivingAccommodation?
    }
}

enum SampleTripsSchemaV2: VersionedSchema {
    static var models: [any PersistentModel.Type] {
        [Trip.self, BucketListItem.self, LivingAccommodation.self]
    }

    @Model
    final class Trip {
        @Attribute(.unique) var name: String
        var destination: String
        var start_date: Date
        var end_date: Date
    
        var bucketList: [BucketListItem]? = []
        var livingAccommodation: LivingAccommodation?
    }
}

enum SampleTripsSchemaV3: VersionedSchema {
    static var models: [any PersistentModel.Type] {
        [Trip.self, BucketListItem.self, LivingAccommodation.self]
    }

    @Model
    final class Trip {
        @Attribute(.unique) var name: String
        var destination: String
        @Attribute(originalName: "start_date") var startDate: Date
        @Attribute(originalName: "end_date") var endDate: Date
    
        var bucketList: [BucketListItem]? = []
        var livingAccommodation: LivingAccommodation?
    }
}

重要なポイント:

  • 各バージョンは独立していますVersionedSchema列挙 -modelsプロパティには、このバージョンに含まれるすべてのモデル タイプがリストされます。
  • version クラスは内部的にバージョンを定義します@Model親切

(07:49) 移行計画を定義します。

enum SampleTripsMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [SampleTripsSchemaV1.self, SampleTripsSchemaV2.self, SampleTripsSchemaV3.self]
    }
    
    static var stages: [MigrationStage] {
        [migrateV1toV2, migrateV2toV3]
    }

    static let migrateV1toV2 = MigrationStage.custom(
        fromVersion: SampleTripsSchemaV1.self,
        toVersion: SampleTripsSchemaV2.self,
        willMigrate: { context in
            let trips = try? context.fetch(FetchDescriptor<SampleTripsSchemaV1.Trip>())
                      
            // De-duplicate Trip instances here...
                      
            try? context.save() 
        }, didMigrate: nil
    )
  
    static let migrateV2toV3 = MigrationStage.lightweight(
        fromVersion: SampleTripsSchemaV2.self,
        toVersion: SampleTripsSchemaV3.self
    )
}

重要なポイント: -schemasすべてのバージョンを順番に並べてリストします -stages2 つのバージョンごとに移行手順を定義する -.custom移行は手動で記述する必要がありますwillMigrateクロージャはデータ変換を処理します -.lightweight移行はフレームワークによって自動的に処理されます (プロパティの名前変更など)。 -willMigrateスキーマが変更される前に実行され、前処理のために古いバージョンのデータをクエリできます。 -didMigrateスキーマ変更後に実行し、移行結果を確認できます

(08:40) アプリの起動時に移行計画を構成します。

struct TripsApp: App {
    let container = ModelContainer(
        for: Trip.self, 
        migrationPlan: SampleTripsMigrationPlan.self
    )
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(container)
    }
}

重要なポイント: -ModelContainer初期化中に渡されるmigrationPlan- フレームワークは現在のデータベースのバージョンを自動的に検出し、必要な移行を順番に実行します。

  • 移行はバックグラウンドスレッドで実行され、完了後にアプリケーションが正常に起動します。

重要ポイント

独自の制約によりデータ同期を簡素化

  • 対処方法: ビジネス固有のフィールドを追加します (ユーザー名、注文番号など)。@Attribute(.unique)- 実行する価値がある理由: upsert セマンティクスにより同期ロジックが簡素化され、最初にクエリを実行してから挿入するか更新するかを決定する必要がありません。
  • 開始方法:@Modelキーフィールドをクラスに追加する@Attribute(.unique)

ビッグ データ プロパティの外部ストレージを有効にする

  • 対処方法: 画像や音声などのバイナリデータの使用@Attribute(.externalStorage)- 実行する価値がある理由: データベース ファイル サイズを削減し、クエリ パフォーマンスを向上させ、ビッグ データ アクセスをより効率的にします。
  • 開始方法: 画像とその他の属性を次のようにマークします。@Attribute(.externalStorage), SwiftData はファイルストレージを自動的に管理します

合理的な削除カスケード戦略を設計する

  • 対処方法: 各親子関係の削除動作を明確にし、次を使用します。@Relationship(.cascade)または.nullify- 実行する価値がある理由: 孤立したデータを回避し、データの整合性を維持します。
  • 開始方法: データ モデル内の関係を分析し、カスケード削除が必要な関係を追加します。@Relationship(.cascade)

バージョン管理されたスキーマと移行計画を早期に確立します

  • やるべきこと: 最初のバージョンから使用されていますVersionedSchema現時点では移行が必要ない場合でも
  • 価値がある理由: 後期の移行計画は、最初から計画するよりもはるかに困難です
  • 開始方法: 定義SchemaV1たとえ内容が単純であっても。後続のバージョンには増分番号が付けられ、移行計画は変更のたびに更新されます。

関連セッション

コメント

GitHub Issues · utterances