SwiftDataのマイグレーション:lightweightとcustomの違い、そしてV2が不要なケース
SwiftDataのスキーマ・マイグレーションは、Core Dataのそれと比べて構造的に大きく改善されています。ただし、多くのチームが繰り返しはまる落とし穴が1つあります。インライン初期値を与えればSwiftDataが自動的に処理してくれる変更に対して、わざわざ新しいVersionedSchemaを宣言してしまうことです。コードは正しく見え、ビルドも通るのに、実機では「Duplicate version checksums across stages detected」というクラッシュが起きます。このフレームワークの実際のマイグレーションモデルは、3つの構成要素(VersionedSchema、MigrationStage、SchemaMigrationPlan)と3種類のマイグレーション(自動lightweight、宣言されたlightweight、custom)から成り立っています1。ほとんどのスキーマ変更は自動で完了します。一部は宣言されたlightweightステージを必要とし、ごく少数だけがwillMigrateとdidMigrateのクロージャを伴うcustomステージを必要とします。
本記事では、Appleのドキュメントに沿ってマイグレーションモデルを追い、それぞれのマイグレーション種別がどのケースを担当するのかを整理します。あわせて、iOS 26のクラス継承サポートと、iOS 27のベータがマイグレーションをどこに位置づけているのかも取り上げます。視点は一貫して「自分で宣言すべきものは何か、SwiftDataが引き受けてくれるものは何か」です。この判断こそが、マイグレーションがきれいに出荷されるか、初回起動でクラッシュするかを分けます。関連する問い、つまりマイグレーションのコストを抑えるためにv1スキーマをどう設計するかについては、SwiftDataの本当のコストはスキーマの規律で扱っています。
要点
- SwiftDataのマイグレーションは3つのプロトコルで構成されます。
VersionedSchema(あるバージョン時点のモデル型のスナップショット)、MigrationStage(.lightweightまたは.customのケースを持つ、fromVersionからtoVersionへの単一の遷移)、そしてSchemaMigrationPlan(ステージの順序付きリスト)です1。 - インライン初期値付きの新しい
@Modelプロパティ(var foo: Bool = false)を追加する場合、新しいVersionedSchemaは不要です。SwiftDataがlightweightマイグレーションとして自動的に処理します。ここでV2を宣言すると、「Duplicate version checksums across stages detected」のクラッシュを招きます。 - lightweightマイグレーションが扱うのは、エンティティ・属性・リレーションシップの追加/リネーム/削除、リレーションシップの型変更、リネームを追跡するための
@Attribute(originalName:)の宣言、そして削除ルールの指定です。ほとんどのスキーマ変更はここに収まります。 - customマイグレーション(
MigrationStage.custom(fromVersion:toVersion:willMigrate:didMigrate:))が扱うのはデータ変換です。1つのカラムを2つに分割する、派生フィールドを計算する、モデル間でデータを移動する、といった処理が該当します。willMigrateには古いコンテキストが、didMigrateには新しいコンテキストが渡されます。 - iOS 26では
@Model型のクラス継承が追加されました2。継承を採用するスキーマは新しいバージョンへ上げ、それ以前のフラットなモデルのバージョンからlightweightステージでつなぎます。
3つの構成要素によるモデル
SwiftDataのマイグレーションは、3つの構成要素から組み立てられます。
VersionedSchema
特定のスキーマバージョン時点における、モデル型のスナップショットです1。このプロトコルが要求するのは次の2つです。
static var versionIdentifier: Schema.Version。セマンティックバージョンの3つ組です(Schema.Version(1, 0, 0))。static var models: [any PersistentModel.Type]。このバージョンに含まれる@Model型の配列です。
enum SchemaV1: VersionedSchema {
static let versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] {
[Item.self]
}
@Model
final class Item {
var name: String
var createdAt: Date
init(name: String, createdAt: Date) {
self.name = name
self.createdAt = createdAt
}
}
}
enumの中に型をネストさせるこのパターンが慣例となっています。各VersionedSchemaが自身のモデルクラスに名前空間を与えるため、同じモデル名を持つ複数のスキーマが、マイグレーション中も同じコードベースに共存できます。
MigrationStage
2つのVersionedSchema型のあいだの、単一の遷移です3。ケースは2つあります。
.lightweight(fromVersion: any VersionedSchema.Type, toVersion: any VersionedSchema.Type)。アプリ側のコードなしでSwiftDataが処理する遷移を宣言します。引数に渡すのはVersionedSchema型そのもの(例:SchemaV1.self)であり、生のSchema.Version値ではありません。.custom(fromVersion:toVersion:willMigrate:didMigrate:)。データマイグレーションの前後(またはいずれか一方)で実行されるコードを伴う遷移を宣言します。バージョン引数の型は.lightweightと同じです。
SchemaMigrationPlan
任意の過去バージョンから現在のバージョンまでスキーマを運ぶ、ステージの順序付きリストです1。
enum AppMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[SchemaV1.self, SchemaV2.self, SchemaV3.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2, migrateV2toV3]
}
static let migrateV1toV2 = MigrationStage.lightweight(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self
)
static let migrateV2toV3 = MigrationStage.custom(
fromVersion: SchemaV2.self,
toVersion: SchemaV3.self,
willMigrate: { context in
// Pre-migration: read old data, prepare it
try context.save()
},
didMigrate: { context in
// Post-migration: backfill new fields
let descriptor = FetchDescriptor<SchemaV3.Item>()
let items = try context.fetch(descriptor)
for item in items {
item.computedField = computeFromExisting(item)
}
try context.save()
}
)
}
ModelContainerは、現在のスキーマとマイグレーションプランの両方を渡して構築します。
let container = try ModelContainer(
for: SchemaV3.Item.self,
migrationPlan: AppMigrationPlan.self,
configurations: ModelConfiguration(...)
)
SwiftDataはコンテナ生成時に永続ストアの現在のスキーマバージョンを読み取り、そのバージョンから現在のバージョンまでプランのステージをたどって、順番に適用していきます。
lightweightマイグレーションが自動で扱う範囲
ほとんどのスキーマ変更に、customステージは必要ありません1。
- 初期値を持つ属性の追加。 既存の
@Modelへのvar foo: Bool = falseは自動で処理されます。 - 新しいエンティティ(モデルクラス)の追加。 その
VersionedSchemaが現行バージョンになった時点で新しい型が現れ、既存データはそのまま保持されます。 - 属性やエンティティの削除。 SwiftDataが該当するカラムまたはテーブルを削除します。
- 属性やエンティティのリネーム。 データを保持するには、プロパティに
@Attribute(originalName: "oldName")を付けます。SwiftDataが旧名から新名へマッピングします。 - リレーションシップの型変更。 1対多、多対多などの変更です。
- 削除ルールの指定。
@Relationship(deleteRule: .cascade)のような追加はlightweightです。
このリストに当てはまる変更では、モデル型に他の変更がないかぎり、新しいVersionedSchemaをまったく宣言しないのが正しいパターンです。SwiftDataは既存のスキーマに対して、lightweightマイグレーションを自動的に実行します。
落とし穴:フィールドの追加にV2は要らない
SwiftDataのマイグレーションで最もよくある誤りがこれです。開発者がインライン初期値付きの新しいプロパティ(var foo: Bool = false)を追加し、続いてSchemaV1と同じモデル型を参照するSchemaV2を宣言してしまう。ビルドは問題なく通ります。ところが、既存のV1データが入った端末での初回起動時にDuplicate version checksums across stages detectedでクラッシュします。SchemaV1とSchemaV2が同じチェックサムに解決されるからです(SwiftDataが差異として認識する形では、モデル型が変わっていないのです)。
正しいパターンはこうです。既存のVersionedSchemaには手を触れず、インライン初期値付きの新しいプロパティをモデルに追加し、SwiftDataの自動lightweightマイグレーションに任せる。MigrationPlanもMigrationStageもV2も必要ありません。
// V1 schema
enum SchemaV1: VersionedSchema {
@Model
final class Item {
var name: String
// BEFORE: just these two properties
var createdAt: Date
// AFTER: add a third with inline default
var isFavorite: Bool = false // Lightweight, automatic
}
}
var isFavorite: Bool = falseという変更は、MigrationStageの宣言なしで出荷できます。migrationPlan:を渡さないModelContainerのイニシャライザで、そのまま動作します。
let container = try ModelContainer(
for: SchemaV1.Item.self,
configurations: ModelConfiguration(...)
)
V2スキーマが必要になるのは、変更がlightweightでは処理できない場合だけです(データ変換、モデルの分割、独自ロジックを要する継承の再構成など)。そうしたケースではV2は実体を伴い、SchemaMigrationPlanが遷移を統括します。
customマイグレーションが必要になるとき
customマイグレーションがその複雑さに見合うのは、次の3つのケースです。
1. 1つのフィールドを複数に分割する。 "Last, First"を保持していたStringのフィールドを、firstNameとlastNameの2つに分けるケースです。マイグレーションでは、古い値を読み、パースし、新しいフィールドへ書き込む必要があります。
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self,
willMigrate: nil,
didMigrate: { context in
let descriptor = FetchDescriptor<SchemaV2.Person>()
let people = try context.fetch(descriptor)
for person in people {
let parts = person.fullName.split(separator: ", ", maxSplits: 1)
person.lastName = String(parts.first ?? "")
person.firstName = String(parts.dropFirst().first ?? "")
}
try context.save()
}
)
didMigrateクロージャは新しいスキーマのコンテキストに対して実行されるため、新しいフィールドにアクセスできます。古いfullNameの削除は、新しいフィールドが埋まるまで先送りする必要があるかもしれません。その後片付けは、V2からV3への後続ステージで行います。
2. 派生フィールドを計算する。 既存データに依存する新しい@Attributeは、マイグレーション時にバックフィルする必要があります。
3. モデル間でデータを移動する。 ItemのデータをItemと新しいTagモデルに振り分ける再編では、古いデータからタグを割り当てる独自ロジックが必要になります。
大まかな指針はこうです。スキーマの形が変わるならlightweight、データの形が変わるならcustom。
willMigrateとdidMigrateの違い
customステージは2つのクロージャを持ち、それぞれ異なるタイミングで呼ばれます4。
willMigrateは、SwiftDataがスキーマのマイグレーションを適用する前に実行されます。クロージャが受け取るモデルコンテキストは、古いスキーマのものです。足元でスキーマが変わってしまう前に、データを取得したり、非正規化したり、補助的な状態を用意したりする用途に使います。
didMigrateは、スキーマのマイグレーションの後に実行されます。モデルコンテキストは新しいスキーマのものです。新しいフィールドのバックフィル、派生データの計算、マイグレーションの仕上げに使います。
不要であれば、どちらのクロージャもnilにできます。多くのcustomマイグレーションはdidMigrateだけを使います。willMigrateが役立つのは、スキーマ変更後にはアクセスできなくなる古いデータを読む必要がある場合です。
クロージャはModelContextを受け取り、フェッチ・変更・保存を行えます。クロージャはthrowingであり、エラーはマイグレーションの外へ伝播して処理を中断させます。
iOS 26:@Modelのクラス継承
iOS 26では、SwiftDataのモデルにクラス継承が導入されました2。モデル同士に、親子関係を持たせられます。
@Model
class Vehicle {
var make: String
var year: Int
init(make: String, year: Int) {
self.make = make
self.year = year
}
}
@Model
final class Car: Vehicle {
var doorCount: Int
init(make: String, year: Int, doorCount: Int) {
self.doorCount = doorCount
super.init(make: make, year: year)
}
}
継承を採用するスキーマは新しいバージョンへ上げ、それ以前のフラットなモデルのバージョンからlightweightマイグレーションステージでつなぎます。継承が既存のプロパティを保っていれば、遷移は自動です。サブクラスに追加する新しいフィールドは、通常のインライン初期値のパターンに従います。
このパターンが向くのは、複数の@Model型が特徴を共有しているケースです。Vehicleを親にCar・Truck・Motorcycleを子とする構成、Accountを親にCheckingAccount・SavingsAccountを子とする構成などが挙げられます。共通のプロパティは親に、個別のプロパティは子に置きます。
iOS 27:マイグレーションモデルは不変、ストアがobservableに
iOS 27のベータは、マイグレーションの仕組みそのものには何の変更も加えていません。VersionedSchema、MigrationStage、SchemaMigrationPlanはそのまま引き継がれ、ここまでのパターンはすべて書かれたとおりに通用します。iOS 27が追加したものは、マイグレーションの内側ではなく、その隣に位置しています。ResultsObserverとHistoryObserverという2つの型からなる新しい「Data store observation」の面、そしてプロパティをCodable表現で保存する@Attribute(.codable)オプションです6。
このうち2つは、マイグレーションのガイドで触れておく価値があります。
@Attribute(.codable)は将来のマイグレーション圧力を下げます。 Codableな値型を宣言的に保存できれば、structを複数のカラムに平坦化し、後からcustomステージを書いて組み立て直す、という場面が減ります。新しいプロパティにこのオプションを採用するスキーマは、前述のインライン初期値のルールに従います。これは属性のオプションであって、スキーマの形の変更ではありません6。
HistoryObserverはcustomマイグレーションの後始末をつなぎます。 didMigrateでのバックフィルは、アプリの他の部分(そしてストアを監視しているウィジェットやextension)が知る必要のある行を書き込みます。iOS 27では、HistoryObserverを通じて永続履歴を監視するオブザーバがマイグレーションのトランザクションの着地を検知し、ModelContext.fetchHistoryを呼んで、モデル型とトランザクションの作成者で絞り込みながら、変わったものだけを正確に読み取れます。世界全体を取り直す必要はありません6。observationの全体像はiOS 27のSwiftData:observationとhistoryで扱っています。
計画上の要点はこうです。27のベータには、スキーマバージョンの引き上げを強いるものは何もなく、既存のマイグレーションコードを書き直す必要もありません。マイグレーション後の突き合わせ処理でポーリングや再フェッチをしていた箇所にこそ、新しいobservationの型を採用しましょう。
マイグレーションをテストする
コンパイルが通るマイグレーションと、出荷できるマイグレーションは別物です。リリース前に実行しておく価値のあるテストパターンが3つあります。
1. 本番データベースのコピーでのラウンドトリップテスト。 最近の本番相当のデータベースを取得し(あるいはテストで合成のV1データを生成し)、V2を認識するコンテナで開いて、データが正しくマイグレートされることを検証します。型チェッカーには捕まえられないcustomマイグレーションのバグを、このテストが拾います。
2. 旧バージョンからの起動確認。 1つ前のアプリバージョンをビルドして一度起動しV1データを作り、そのうえで新しいアプリバージョンをビルドして、クラッシュせずに起動することを確認します。「Duplicate version checksums」の落とし穴や、同種の宣言ミスを捕まえられます。
3. マイグレーション失敗からの復帰。 マイグレーションがthrowしたら、何が起きるでしょうか。SwiftDataの挙動はコンテナの構成に依存します。本番アプリでは、処理されなかったマイグレーションエラーがユーザーのデータを黙って消してしまうようなことがあってはなりません。失敗経路を明示的にテストし、アプリがどう振る舞うか(ロールバック、ユーザーへの確認、バックアップからの復元)を決めておきましょう。
同じクラスタのSingle Source of Truthの記事では、プロセスをまたぐ同期によってSwiftDataのストアが置き換わったときに何が起きるか、という関連する問いを扱っています。マイグレーションは、そのパターンをローカルでの進化に置き換えたものだと言えます。
プロセスをまたぐ出荷と、進捗の見せ方
ドキュメントでは前面に出ていないものの、SwiftDataチームがWWDC 2026で明言した運用上の詳細が2つあります5。ウィジェットやextensionを持つアプリでマイグレーションがどこで走るのか、そして走っているあいだ進捗UIをどう動かすのか、です。
マイグレーションを所有するのは1つのプロセスだけ。 ウィジェットやextensionには、メインアプリと同じだけの実行リソースが与えられません。そのため、マイグレーションを安全に実行できないのです。指針としては、SchemaMigrationPlan1をウィジェットやextensionのターゲットからは完全に外し、そこからは決してマイグレーションしないことです。データベースの所有者となるプロセスを1つ、通常はメインアプリを選びます。ウィジェットがコンテナを開いたときに、ディスク上のストアがバージョン管理されていない(古い)スキーマのままであれば、オープンはエラーになります。そのエラーを「マイグレーションが必要だ」という合図として扱ってください。メインアプリを開くようユーザーに促すUIを出し、アプリにマイグレーションを実行させ、マイグレート済みのスキーマバージョンを共有のUserDefaultに書き込ませます。ウィジェットは次回その値を読み、アプリが既にマイグレートしたバージョンでコンテナを開きます。このパターンなら書き手を1つに保てるので、2つのプロセスが同じファイルを競って進化させる事態を避けられます。
進捗は経過時間ではなく、ステージ数から算出する。 SwiftDataには、マイグレーション専用の進捗APIがありません5。進捗インジケータを動かすには、プラン内のcustomマイグレーションステージの総数を数え、ステージごとのdidMigrateハンドラをオーバーライドして4、各ステージに「M個中N個目」という位置を報告させます。この数値は完了したステージ数であって経過時間ではないため、バーはなめらかにではなく、離散的に進みます。あわせて考えるべき設計上の判断は、マイグレーション中にアプリが何を見せるかです。素っ気ないスピナーは処理が止まっているように見え、ユーザーは離脱します。データが許すかぎりアプリを部分的にでも使える状態に保つか、少なくとも各ステージが何を追加しているのか(そのマイグレーションで解禁される新機能)を説明しましょう。そうすれば待ち時間が、死んだ時間ではなく、何かへ向かう進捗として読まれます。
よくある失敗パターン
SwiftDataの失敗ログから、3つのパターンを挙げます。
SwiftDataが自動で処理する変更にV2を宣言してしまう。 「Duplicate version checksums」のクラッシュです。対処:インライン初期値付きプロパティの追加のために新しいスキーマを宣言せず、SwiftDataに自動で処理させます。
保存しないcustomマイグレーションコード。 エンティティを変更しておきながらcontext.save()を呼ばないdidMigrateクロージャは、一度実行しては作業結果を捨て、起動のたびに再実行されるマイグレーションを生みます(マイグレーションが未完了に見えるためです)。対処:データを変更するクロージャは、必ずreturn前にtry context.save()を実行します。
@Attribute(originalName:)なしでプロパティをリネームする。 SwiftDataは新しいプロパティを新規、古いプロパティを削除として扱うため、古いプロパティにあった既存データは失われます。対処:@Attribute(originalName: "oldName") var newName: ...と宣言し、リネームをまたいでデータをマッピングさせます。
iOS 26以降のアプリにとって、このパターンが意味すること
要点は3つです。
-
既定では
VersionedSchemaの階段を作らない。 インライン初期値付きのプロパティ追加、使わなくなったフィールドの削除、@Attribute(originalName:)によるリネーム。どれもlightweightで自動です。VersionedSchemaの階段は、SwiftDataが本当に自動では処理できない変更(データ変換、独自ロジック、継承の再構成)のためにあります。 -
MigrationStage.customはデータ変換のために使い、スキーマの形の変更には使わない。willMigrateとdidMigrateのクロージャは、データを操作するコードのためのものであって、スキーマが変わったことを宣言するためのものではありません。スキーマの形の変更は、lightweightステージを通します。 -
マイグレーションは合成のテストデータだけでなく、実際のV1データでテストする。 合成データのラウンドトリップでは通っても、本番相当のデータではエッジケース(スキーマが想定していなかったnull許容フィールド、タイムアウトに達する大規模データなど)で落ちることがあります。テストのコストは小さく、初回起動でマイグレーションがクラッシュするコストは現実の損害です。
Apple Ecosystemクラスタの全体像はこちらです。型付きのApp Intents、MCPサーバー、ルーティングという問い、Foundation Models、ランタイムLLMとツーリングLLMの区別、3つのサーフェス、single source of truthパターン、2つのMCPサーバー、Apple開発のためのhooks、Live Activities、watchOSのランタイム、SwiftUIの内部、RealityKitの空間メンタルモデル、SwiftDataのスキーマの規律、Liquid Glassのパターン、マルチプラットフォームでの出荷、プラットフォームマトリクス、Visionフレームワーク、Symbol Effects、Core MLによる推論、Writing Tools API、Swift Testing、Privacy Manifest、プラットフォーム機能としてのアクセシビリティ、SF Proのタイポグラフィ、visionOSの空間パターン、Speechフレームワーク、書かないと決めていること。ハブはApple Ecosystemシリーズにあります。iOSとAIエージェントを組み合わせる、より広い文脈についてはiOS Agent Developmentガイドをご覧ください。
よくある質問
SchemaMigrationPlanは必ず必要ですか?
いいえ。スキーマバージョンが1つしかないアプリ(初回リリース、あるいはlightweightな変更しかしてこなかったアプリ)に、SchemaMigrationPlanは不要です。ModelContainerのイニシャライザは、スキーマのモデルを直接受け取ります。migrationPlan:引数が必要になるのは、customマイグレーションステージを初めて宣言するとき(あるいは、明示的なバージョンの階段を初めて宣言したくなったとき)です。
自分の変更がlightweightかどうかは、どう判断すればよいですか?
Appleが示すlightweight対象のリストはこうです1。エンティティ・属性・リレーションシップの追加、それらの削除、@Attribute(originalName:)によるリネーム、リレーションシップの多重度の変更、削除ルールの指定。変更がこのいずれかに当てはまり、モデルクラスの構造に他の変更がなければ、マイグレーションは自動で行われ、VersionedSchemaの階段は必要ありません。データ変換(計算、分割、データの移動)が必要な変更であれば、customになります。
willMigrateとdidMigrateは両方設定できますか?
はい。2つのクロージャはそれぞれ個別にオプションですが、両方を指定することもできます。willMigrateはSwiftDataがマイグレートする前に、古いスキーマのコンテキストに対して実行されます。didMigrateはその後に、新しいスキーマのコンテキストに対して実行されます。前者が準備を、後者が仕上げを担います。
マイグレーションがエラーをthrowするとどうなりますか?
エラーはModelContainerの初期化から外へ伝播し、コンテナのオープンは失敗します。その後のアプリの挙動は、開発者がエラーをどう扱うか次第です。復旧用のUIを表示するアプリもあれば、バックアップからの復元を試みるアプリ、壊れたストアを削除してやり直すアプリもあります。SwiftDataがマイグレーション失敗時にユーザーのデータを黙って削除することはありません。失敗の扱いは、アプリ側の責任です。
本番データに影響を与えずにマイグレーションをテストするには?
一時ファイルのURLを指すModelContainerを作るテストターゲットを用意し、そこにV1データを投入したうえで、マイグレーションプランを含む新しいコンテナで開きます。マイグレート後のデータが期待どおりかを検証してください。このパターンはユニットテストでも結合テストでも使えます。最も現実に近い結果を得たいなら、実際の本番相当のデータベースのコピーを使いましょう。
iOS 26のクラス継承は、既存のスキーマでも使えますか?
はい、lightweightマイグレーションを伴って使えます。継承を採用するアプリは新しいスキーマバージョン(例:V4)へ上げ、MigrationStage.lightweight(fromVersion: V3.self, toVersion: V4.self)を宣言します。フラットな親クラスのプロパティはそのまま残り、サブクラス固有のプロパティはインライン初期値付きで追加されます。この構造上の変化は、SwiftDataのlightweightマイグレーションが処理します。
参考文献
-
Apple Developer Documentation:
VersionedSchemaおよびSchemaMigrationPlanのプロトコルリファレンス。マイグレーションモデルの解説です。スキーマ進化の全体像については、関連ガイドAdopting SwiftData for a Core Data appもあわせてご覧ください。 ↩↩↩↩↩↩↩↩ -
Apple Developer:SwiftData: Dive into inheritance and schema migration(WWDC 2025 セッション291)。iOS 26におけるSwiftDataのクラス継承の導入について。 ↩↩
-
Apple Developer Documentation:
MigrationStage。.lightweight(fromVersion:toVersion:)と.custom(fromVersion:toVersion:willMigrate:didMigrate:)のケースについて。 ↩ -
Apple Developer Documentation:ケースのシグネチャは
MigrationStage.custom(fromVersion:toVersion:willMigrate:didMigrate:)を参照してください。willMigrateが古いコンテキストに対して、didMigrateが新しいコンテキストに対して実行されるというセマンティクスは、WWDC 2025 セッション291のSwiftData: Dive into inheritance and schema migrationに記載されています。iOS 26の継承追加で参照したものと同じセッションです。 ↩↩↩ -
WWDC 2026 SwiftData Group Lab(セッション8017)。WWDC 2026 SwiftData Group Labをローカルで文字起こしした録音からの要約です。Appleはラボの公式字幕を公開していません。ウィジェットとextensionにおけるマイグレーションの制限(マイグレーションを所有するのは1つのプロセス、エラーがマイグレーションの合図、マイグレート済みバージョンは
UserDefaultに保存)と、ステージ数による進捗の手法(専用の進捗APIが存在しないため、ステージごとのdidMigrateハンドラをオーバーライドしてM個中N個目を報告する)は、SwiftDataのエンジニアリングパネルが説明したものです。SchemaMigrationPlanとMigrationStage.customのdidMigrateというシンボルは、1および4で引用したApple Developerのドキュメントと照合済みです。専用の進捗APIが存在しないという点は、ラボでのパネル自身の説明を反映しています。 ↩↩ -
Apple Developer Documentation:
ResultsObserverとHistoryObserver(いずれもiOS 27.0ベータ、SwiftDataの「Data store observation」トピック)、およびSchema.Attribute.Option.codable(iOS 27.0ベータ)。「プロパティの保存にそのcodable表現を用いる」とあります。WWDC26 セッション274のWhat’s new in SwiftDataによれば、HistoryObserverは新しいトランザクションが着地するたびに増加するobservableなeventCounterを公開し、コード側はモデル型とトランザクションの作成者のフィルタを指定してModelContext.fetchHistoryを呼ぶことで応答します。 ↩↩↩