ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Core Dataの新機能
Core Dataの改善によって、アプリのデータ永続性を改善させましょう。複合属性を使用して、より直感的なデータモデルを作成する方法について学びましょう。また、大幅な変更を伴うスキーマの移行方法、負荷のかかる移行のタイミングを遅らせたり、およびユーザーのデバイスに対する負荷を避ける方法も紹介します。このセッションを最大限に活用するためには、Core Dataで異なるデータ型を扱う方法と、軽量マイグレーションの基本を理解していることが望ましいです。
関連する章
- 0:00 - Intro
- 0:56 - Composite attributes
- 6:31 - Stage your migrations
- 18:23 - Defer your migrations
- 22:33 - Wrap-up
リソース
関連ビデオ
WWDC22
-
ダウンロード
♪ ♪
David:「Core Dataの新機能」へようこそ 私 David Stitesは Core Dataチームのエンジニアです このセッションでは アプリ内の Core Dataデータモデルを より迅速かつ簡単に設計 クエリ 更新および移行する際に役立つ Core Dataの新技術を学びます
まず アプリのモデルで 構造化データを整理する 優れた新方法である 複合属性についてお話しします 次に軽量なマイグレーションを 使用するために 最も複雑なモデルマイグレーションを 段階的なものに変える方法をお伝えします 最後に アプリの応答性を保つために モデルの移行を 延期する方法についてお話しします 複合属性は新しいタイプの属性です
複合属性によって単一の属性内に 複雑なカスタムデータ型の カプセル化が可能になります
各複合属性は String Float Int Dataなど すでにお馴染みの 組み込みCore Data型の 属性で構成されます
複合属性は最上位の複合属性が さらに複合属性を含むように 互いに入れ子にすることができます
Xcode Core Dataモデルエディタは モデルの複合属性の定義と管理を 容易にするため更新されました
複合属性は 耐久性のあるカスタムデータ型を 作成するために 変換可能な型属性を 使用するための説得力のある代替手段です 属性の値を変換するコードを 書く必要はありません 変換可能な属性とは異なり 複合属性は 複合属性の名前空間付きキーパスで 構成されたNSPredicatesを持つ NSFetchRequestsを許可します
複合属性はフラット化された属性の拡張を カプセル化するために 使用することができ 保守性と可読性の向上につながります
複合属性はアプリのパフォーマンス 向上にも使用できます データモデルが あるエンティティを取得することで ほかのエンティティへの関連アクセスが 生じるように構成されている場合 その関連をコンポジット属性を使用して リファクタリングすることができます 複合属性を最初のエンティティに 埋め込むことにより リレーションシップを越えたオブジェクトの 不具合を防ぐことができます
複合属性クラスは NSComposite AttributeDescription です NSCompositeAttribute Descriptionの属性タイプは NSCompositeAttributeTypeです
NSComposite AttributeDescriptionクラスは NSAttributeDescriptionまたは ほかの入れ子になった NSCompositeAttribute Descriptionの配列 要素を含みます
要素配列は NSRelationship Descriptionのような ほかのタイプのプロパティ記述を 含むことはできません 無効な要素を設定しようとするとNSInvalid ArgumentExceptionが発生します
複合属性の実装方法について コードを使って説明します
この基本的なデータモデルを 航空機のエンティティで考えてみましょう 多くの属性を有し 中でもcolors属性は 変形可能なタイプです そのタイプのトランスフォーマーは 航空機のprimary secondary tertiaryの色を記述する文字列を 格納し 解析します
このエンティティを改良するために colors属性を colorSchemeという 複合属性に置き換えて 航空機の塗装色を保存します
colorSchemeは次の要素を持つ 複合属性です primary secondary tertiaryは それぞれString属性です
Xcodeで飛行時間を記録するために 使用しているアプリの プロジェクトを開きます
このアプリのデータモデルには 先ほど説明した Aircraftエンティティとその他の エンティティがいくつか設定されています
変換を始めるために Core Dataモデルエディタで colorSchemeという 新しい複合属性を追加します
そのコンポジット内にprimary secondary tertiaryの 3つの文字列属性を追加しています
Aircraftエンティティに composite属性を追加し その属性のタイプを colorSchemeに設定します
モデル内の作業は完了したので コードを更新する番です
Aircraftの実装では @NSManaged var colorSchemeという 新しいプロパティを追加しています コード全体で この複合属性を使用しているため 属性名をキーとして 辞書記法で値にアクセスしています primar secondary tertiaryという 文字列のキーを使用して 航空機の colorScheme属性を設定しています
同様に、NSFetchRequestを NSPredicateで構成すると 複合属性の要素は名前空間付きキーパスを 介してアクセスされます
ここではcolorScheme.primaryで その属性をフィルタリングしています
アプリケーションが進化するにつれて データモデル変更を要する可能性があります
データモデルを更新するには その変更を基礎となる ストレージスキーマに 具体化する必要があります
モデルにnumPassengers属性が 追加された場合 対応するストレージを 更新する必要があります
スキーマの変更を行うプロセスを マイグレーションと呼びます
マイグレーション後 変更は 基礎となるストレージに完全に反映されます
Core Dataにはアプリケーションの データストレージを最新データモデルに 保つためのマイグレーションツールセットが 組み込まれています これらのツールを総称して 「軽量マイグレーション」と呼びます
軽量なマイグレーションについては WWDC 2022の「Evolve your app's schema」をご覧ください
データモデルへの複合的な変更が 軽量マイグレーションの能力を 超えることもあります この問題の解決策は 段階的な移行です
段階的移行用のAPIはいくつかの目標を 念頭に置いて設計されています 軽量のスキーマ変更に適合しない 複雑なデータモデルの移行を支援し 移行と移行インフラストラクチャに関連する 数千行のコードを削除する可能性があるため アプリケーションを簡素化できます また 移行プロセス中に アプリが実行状態をコントロールできるため さまざまな他のタスクを 実行する機会を提供します
このAPIを使用するにはいくつか ステップを踏む必要があります まず モデルの変更が 軽量マイグレーションで サポートできない規模であることを 判断します 軽量マイグレーションに合っていないモデル変更を 軽量マイグレーションで実行できる程度の モデル変更の規模に分解します 新しい段階的移行の API を使用した NSManagedObjectModelの Core Dataへの完全な 順序付けについて説明します Core Dataがイベントループを実行し 未処理の各モデルを 直列順序で繰り返し処理し ストアをマイグレーションします マイグレーション中の特定の時点で 実行制御があなたの アプリケーションに与えられ 移行に関連する必要なタスクが実行されます
軽量マイグレーションで対応できるモデル変更かを 判断するには いくつかの選択肢があります 最初の選択肢は スキーマの変更を手作業で確認し 各変更が軽量移行の対象であることを 確認するものです
2つ目の選択肢は新しいモデルと 軽量マイグレーションオプションである NSMigratePersistentStores AutomaticallyOption および NSInferMappingModel AutomaticallyOptionをtrueに 設定して 永続ストアを開くことです 変更が軽量マイグレーションの 対象でない場合は NSPersistentStore Incompatible VersionHashErrorが発生します
最後の選択肢はNSMappingModel. inferredMappingModel (forSourceModel:destinationModel:)を 使用することです このメソッドは、もしCore Dataが実行できる場合、 推論モデルを返します。 そうでない場合はnilを返します
Aircraftモデルを再度考慮すると 新属性である flightDataを有しており バイナリ形式でデータを保存します
仮にこのモデルを非正規化し すべてのフライトデータを 独自のエンティティタイプに 分離する必要があり 同時に既存のデータとそれが生成された 航空機との関係を保持する場合を考えます これは非常に複雑なモデルチェンジであり 軽量マイグレーションの対象になりません 段階的マイグレーションを使用するには これらの変更を分解する必要があります 軽量でない変更を分解する場合 目標は軽量マイグレーションの対象外の マイグレーションタスクを 軽量マイグレーションの 対象となる最小限の一連の マイグレーションに変換することです
複雑な変更を実現するために 導入された各モデルは 軽量マイグレーションの能力で対応可能なレベルで 1つ以上のオペレーションを 持つことになります その結果 各モデルが 軽量マイグレーション可能ですが 複雑なマイグレーションと同等の結果をする 一連のマイグレーションとなります
元のモデルにModelV1という ラベルを付けました
このモデル移行はModelV2と ModelV3という2つの新しい モデルバージョンを 導入することで分解できます
ModelV2ではAircraftエンティティは 新作成のFlightDataエンティティの コレクションであるflightParameters リレーションシップを獲得します FlightData エンティティは バイナリ型の属性データと Aircraft との関係を持ちます 既存データを保持するため 移行ステージでデータを Aircraftエンティティから 新しいFlightDataエンティティに コピーしてAircraftに関連付けます
最終的なモデルは ModelV2から作られたModelV3です ModelV3では Aircraftエンティティから 古いflightData属性が削除され モデルの非正規化に成功し 既存のデータはすべて保持されます 説明した各ステップは 軽量マイグレーションの能力の範囲内です
モデルの全体的な順序を記述するために Core Dataフレームワークレベルの サポートは 以下のクラスで構成されます NSStagedMigrationManager NSCustomMigrationStage NSLightweightMigrationStage および NSManagedObject ModelReferenceです
NSStagedMigrationManager クラスは NSCustomMigrationStageと あなたが記述したNSLightweight MigrationStageの 補足の合計順序をカプセル化します 段階的移行マネージャーは 移行イベントループも管理し NSPersistentContainerを介して 移行ストアへのアクセスも提供します マネージャーを ストアオプションに追加する際は NSPersistentStoreStaged MigrationManagerOptionKeyを使います
移行ステージはモデルのバージョン間の 移行の基礎を形成します
段階的移行を採用する場合 NSCustomMigrationStageまたは NSLightweight MigrationStageを使用して 各モデルのバージョンを Core Dataに記述します NSLightweight MigrationStageクラスは 分解を必要とせず 軽量マイグレーションが可能な 一連のモデルを記述します これはおそらくあなたのモデルの 大部分を占めるでしょう これらの軽量な移行ステージは コアデータに記述された― モデルの総順序を 補足するために使用されます すべての軽量モデルのバージョンは 1つ以上の NSLightweightMigrationStageで 表現する必要があります
作成したモデルの各分解バージョンは NSCustomMigrationStag を 使って表現され ソースおよびデスティネーション モデルリファレンスを含みます
NSCustomMigrationStageは 移行ステージの直前と直後に実行される オプションのハンドラを提供します これらのハンドラーは移行処理中に カスタムコードを実行する機能を提供します
段階的移行はNSManagedObjectModel Reference クラスを使用します このクラスは NSManagedObject Modelの実行計画を表します 移行中 Core Dataが この実行を果たします NSManagedObjectModel Referenceは柔軟性があり さまざまな方法で作成ができます
すべての NSManagedObject ModelReferenceを 初期化する必要があります これによりモデルが不注意に 変更されていないことを確認します チェックサムの取得には NSManagedObjectModel .versionChecksumメソッドを使います
あるいは Xcode のビルドログの 「Compile data model」から バージョンチェックサムを 取得することもできます 「version checksum」という 文字列を検索するのです
バージョン管理されたモデルの場合 チェックサムは NSManagedObjectModelバンドルの VersionInfo.plistでも利用可能です
例に戻って 段階的移行を使い始めるために 3つのモデルそれぞれに モデル参照を作成することから始めます 私はモデル名とバンドルリファレンスを 受け取るイニシャライザを 使用していますが ほかの選択肢もあります
次のステップは 必要な移行段階を説明することです 最初のステージでは flightData属性が追加されただけなので 属性の追加は軽量な変更であるため 軽量なステージで表すことができます
しかし、次の段階は カスタムステージになります モデルの変更が2つのモデルの バージョンに分解されたため 既存のデータを保持するために カスタムコードを実行する必要があります カスタム移行ステージはModelV2と ModelV3で初期化されます
willMigrateHandlerでは flightDataが nilでないエンティティ行を フェッチしています 汎用 NSManagedObjectおよび NSFetchRequestResult型は Aircraft管理オブジェクトの サブクラス代わりに使われています 移行中にAircraftクラスが 期待通りに 存在しない可能性があるためです
フェッチされた Aircraftエンティティごとに データはFlightDataの 新しいインスタンスにコピーされ 2つのエンティティは関連付けられ 永続化されます この移行ステージの実行が終了すると ストアのスキーマは 最新のモデルに更新され 既存のデータは保持されます
段階的移行を完了するため 軽量な移行ステージと カスタム移行ステージを持つNSStaged MigrationManagerを作成します
NSStagedMigrationManagerは NSPersistentStore Staged MigrationManagerOptionKeyを持つ NSPersistentStoreDescription オプションに追加されます
そのあと 永続ストアがロードされて 移行プロセスが開始し ストアスキーマに影響を与えます 以上です Core Dataは 自動的に必要なステージを適用し ストアスキーマを移行します
軽量移行の中には アプリケーションがフォアグラウンドで 提供できないような追加のランタイムを 必要とするものがあります
軽量移行でユーザーデータを 変換するプロセスは 即座に完了するものではありません 例えば あるカラムから別のカラムへ またはあるテーブルから別のテーブルへ データをコピーするような移行であれば 時間がかかるかもしれません
特に起動時に移行が行われる場合 じれったいような ユーザーエクスペリエンスに つながる可能性があります
移行延期はこの問題の解決に役立ちます このAPIを使えば 軽量移行中に行われる 作業の一部を延期し 延期した作業を 後日完了させることができます 軽量移行中 エンティティにインデックスを更新したり テーブルコピーを実行したあとに 列を削除したりするなど クリーンアップが必要な マイグレーション変換がある場合 このテーブル変換は テーブル変換を実行するためのリソースが 利用可能と判断されるまで 延期することができます 軽量マイグレーションは依然として同期的で 正常に行われます スキーマのクリーンアップだけが 延期されます アプリケーションは通常通り 最新のスキーマを使用します Deferredマイグレーションを選択する際は NSPersistentStoreDeferred LightweightMigrationOptionKeyを ストアオプションでtrueに設定します
DeferredマイグレーションAPIは macOS Big Surと SurとiOS 14までさかのぼり ランタイム互換性があります
Deferredマイグレーションは SQLiteストアタイプでのみ利用可能です
Deferredマイグレーションが有用な例を いくつか挙げてみます エンティティからの属性や リレーションシップの削除 エンティティ階層が存在しなくなった リレーションシップの変更 リレーションシップを順序付きから 非順序付きに変更する
延期した移行タスクを終了するには 永続ストアのメタデータを確認します NSPersistentStore DeferredLight weightMigrationOptionKeyが 含まれている場合は 完了すべきマイグレーション作業が 遅延していることを示しています Deferredマイグレーションは NSPersistentStoreCoordinator.finishDeferredLightweightMigrationを 呼び出すと処理できます
アプリケーションで 軽量マイグレーションを延期するには コーディネータに永続ストアを追加する際に ストアオプションで NSPersistentStoreDeferredLightweight MigrationOptionKeyをTrueに設定します
Deferredマイグレーションを終了する 適切なタイミングになると ストアのメタデータをチェックすることで 保留中の延期作業の有無を確認できます NSPersistentStoreDeferred LightweightMigrationOptionKeyが true設定の場合はfinishDeferred LightweightMigration()を呼び出します
Deferredマイグレーションのタスクを スケジュールするには Background Tasks APIの使用を 検討してください BGProcessingTaskは 長時間のデータ更新やアプリケーションの メンテナンスなど 時間のかかる操作のためのものです タスクの実行に最適な時間を システムが判断します しかし 一般的に処理タスクは デバイスがアイドル状態の時にのみ実行され ユーザーがデバイスを使い始めると バックグラウンド処理タスクは終了します
Deferredおよび段階的移行は 組み合わせることができます 時間を要するような複雑な移行がある場合は 両APIの機能を利用する ステージの設計を検討してください 例のモデルに戻ると ModelV3で flightData属性を削除する場合 これは良いDeferredマイグレーションの 候補になるかもしれません
Core Dataには 3つの素晴らしい新技術があります コンポジット属性を使用して ネスト可能で構造化された方法で カスタムデータ型をカプセル化し モデルの変更を分解して 段階的移行を使用し 複雑なモデル移行を実行し Deferredマイグレーションを使用して 一部の移行作業を遅延させることで アプリケーションのパフォーマンスを 向上させます 3つのテクノロジーはすべて調和して機能し アプリケーションを改善します
私たちのチームは皆さんがこの新技術を どのように使用するか楽しみです ご視聴ありがとうございます 引き続き WWDCをお楽しみください ♪ ♪
-
-
5:39 - Adding a composite attribute
enum PaintColor: String, CaseIterable, Identifiable { case none, white, blue, orange, red, gray, green, gold, yellow, black var id: Self { self } } extension Aircraft { @nonobjc public class func fetchRequest() -> NSFetchRequest<Aircraft> { return NSFetchRequest<Aircraft>(entityName: "Aircraft") } @NSManaged public var aircraftCategory: String? @NSManaged public var aircraftClass: String? @NSManaged public var aircraftType: String? @NSManaged public var colorScheme: [String: Any]? @NSManaged public var photo: Data? @NSManaged public var tailNumber: String? @NSManaged public var logEntries: NSSet? }
-
5:53 - Setting a composite attribute
private func addAircraft() { viewContext.performAndWait { let newAircraft = Aircraft(context: viewContext) newAircraft.tailNumber = tailNumber newAircraft.aircraftType = aircraftType newAircraft.aircraftClass = aircraftClass newAircraft.aircraftCategory = aircraftCategory newAircraft.colorScheme = [ "primary": primaryColor.rawValue, "secondary": secondaryColor.rawValue, "tertiary": tertiaryColor.rawValue ] do { try viewContext.save() } catch { // ... } } }
-
6:11 - Fetching a composite attribute
private func findAircraft(with color: String) { viewContext.performAndWait { let fetchRequest = Aircraft.fetchRequest() fetchRequest.predicate = NSPredicate(format: "colorScheme.primary == %@", color) do { var fetchedResults: [Aircraft] fetchedResults = try viewContext.fetch(fetchRequest) // ... } catch { // Handle any errors that may occur } } }
-
16:00 - Creating managed object model references for staged migration
let v1ModelChecksum = "kk8XL4OkE7gYLFHTrH6W+EhTw8w14uq1klkVRPiuiAk=" let v1ModelReference = NSManagedObjectModelReference( modelName: "modelV1" in: NSBundle.mainBundle versionChecksum: v1ModelChecksum ) let v2ModelChecksum = "PA0Gbxs46liWKg7/aZMCBtu9vVIF6MlskbhhjrCd7ms=" let v2ModelReference = NSManagedObjectModelReference( modelName: "modelV2" in: NSBundle.mainBundle versionChecksum: v2ModelChecksum ) let v3ModelChecksum = "iWKg7bxs46g7liWkk8XL4OkE7gYL/FHTrH6WF23Jhhs=" let v3ModelReference = NSManagedObjectModelReference( modelName: "modelV3" in: NSBundle.mainBundle versionChecksum: v3ModelChecksum )
-
16:19 - Creating migration stages for staged migration
let lightweightStage = NSLightweightMigrationStage([v1ModelChecksum]) lightweightStage.label = "V1 to V2: Add flightData attribute" let customStage = NSCustomMigrationStage( migratingFrom: v2ModelReference, to: v3ModelReference ) customStage.label = "V2 to V3: Denormalize model with FlightData entity"
-
16:54 - willMigrationHandler and didMigrationHandler of NSCustomMigrationStage
customStage.willMigrateHandler = { migrationManager, currentStage in guard let container = migrationManager.container else { return } let context = container.newBackgroundContext() try context.performAndWait { let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Aircraft") fetchRequest.predicate = NSPredicate(format: "flightData != nil") do { var fetchedResults: [NSManagedObject] fetchedResults = try viewContext.fetch(fetchRequest) for airplane in fetchedResults { let fdEntity = NSEntityDescription.insertNewObject( forEntityName: "FlightData, into: context ) let flightData = airplane.value(forKey: "flightData") fdEntity.setValue(flightData, forKey: “data”) fdEntity.setValue(airplane, forKey: "aircraft") airplane.setValue(nil, forKey: "flightData") } try context.save() } catch { // Handle any errors that may occur } } }
-
17:41 - Loading the persistent stores with an NSStagedMigrationManager
let migrationStages = [lightweightStage, customStage] let migrationManager = NSStagedMigrationManager(migrationStages) let persistentContainer = NSPersistentContainer( path: "/path/to/store.sqlite", managedObjectModel: myModel ) var storeDescription = persistentContainer?.persistentStoreDescriptions.first storeDescription?.setOption( migrationManager, forKey: NSPersistentStoreStagedMigrationManagerOptionKey ) persistentContainer?.loadPersistentStores { storeDescription, error in if let error = error { // Handle any errors that may occur } }
-
21:01 - Adding a persistent store with NSPersistentStoreDeferredLightweightMigrationOptionKey option
let options = [ NSPersistentStoreDeferredLightweightMigrationOptionKey: true, NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true ] let store = try coordinator.addPersistentStore( ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options )
-
21:17 - Executing deferred migrations
// After using BGProcessingTask to run migration work let metadata = coordinator.metadata(for: store) if (metadata[NSPersistentStoreDeferredLightweightMigrationOptionKey] == true) { coordinator.finishDeferredLightweightMigration() }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。