LoginSignup
14
10

More than 1 year has passed since last update.

【Core Data】LightWeight Migrationのやり方

Posted at

はじめに

個人でリリースしたアプリでCore DataCloudKitを使用しており、機能追加に伴いCore Dataを修正していくことになりました。Core Data のマイグレーションはドキュメントや記事を読む限り、そんなに難しくなさそうですが、しっかり理解したく記事にまとめました。

環境

Xcode 13.2.1
Swift 5.5.2

ドキュメントを読む

ドキュメント:Core Data - Light Weight Migration

Data Migrationの種類

1. Light Weight (automatic) Migration
Light Weight(自動)マイグレーションにより、アプリの変更に合わせたデータモデルの更新が可能。
2. Heavy Weight (manual) Migration
データモデルへの変更が自動マイグレーションの能力を超える場合は、Heavy Weight(手動)マイグレーションを使用する。

Light Weight Migrationとは

ドキュメント: Using Lightweight Migration

Core Dataは通常、軽量マイグレーションと呼ばれる自動データ移行を行うことができます。軽量マイグレーションは、移行元と移行先のマネージドオブジェクトモデルの違いからデータ移行を推測して行う。

■ Core Dataは以下の条件で、永続的なエンティティやプロパティに対するスキーマの変更を分析し、推論されたマッピングモデルを生成しデータ移行する。

  • 属性(Attribute)の追加
  • 属性(Attribute)の削除
  • 属性(Attribute)をオプショナルへ変更
  • 属性(Attribute)を非オプショナルへ変更(デフォルト値設定)
  • エンティティ・プロパティの名前変更

■ エンティティやプロパティの名前を変更した場合、変更先モデルの名前変更識別子を、変更元モデルの対応するプロパティやエンティティの名前に設定することでデータ移行を行う。

■ 新しいリレーションシップを追加したり、既存のリレーションシップを削除したりすることができる。また、属性と同じように、リネーム識別子を使用してリレーションシップの名前を変更することもできる。さらに、リレーションを一対一から多対一に変更したり、非順序型から順序型に変更したり(その逆も可能)することができる。

■ 階層内のエンティティの追加、削除、名前の変更が可能。また、新しい親エンティティまたは子エンティティを作成し、エンティティ階層の上下にプロパティを移動することができる。階層からエンティティを移動することができる。ただし、エンティティ階層をマージすることはできない。既存の2つのエンティティがソースで共通の親を共有していない場合、それらのエンティティがデスティネーションで共通の親を共有することはできない。

Light Weight Migrationができるか確認する

実際に移行作業を行わずに、Core Dataが移行元モデルと移行先モデルの間のマッピングモデルを推論できるかどうかを事前に判断したい場合は、NSMappingModelinferredMappingModel(forSourceModel:destinationModel:)メソッドを使用し、Core Data が作成可能な場合は推論されたモデルが取得できる。

Light Weight Migrationの実装方法

addPersistentStore(ofType: configurationName: at: options:) を使用して、自動軽量化移行を要求する。尚、パラメータで渡すオプションはNSMigratePersistentStoresAutomaticallyOptionおよび NSInferMappingModelAutomaticallyOptionの両方のキーに対応する値を true に設定する。

let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
do {
    try psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options)
} catch {
    fatalError("Failed to add persistent store: \(error)")
}

これらの設定により、Core Dataは永続ストアが現在のモデルにマッチしなくなったことを検出すると、軽量マイグレーションを試行します。

以上がドキュメントから確認できた内容です。

新しいDataModelの追加と切り替え

DataModelの追加

Editor > Add Model Version から簡単に追加できます
スクリーンショット 2022-03-01 20.36.53.png
Version nameも初期値をそのまま使用し、Based on modelは現在のDataModelが指定されています
スクリーンショット 2022-03-03 6.30.23.png
これで新しいDataModelが追加されました

DataModelの切り替え

Model Version Currentを新しく追加したDataModelにします
スクリーンショット 2022-03-03 6.42.07.png
緑色のマークが新しく追加したDataModelについていればOKです

新しく追加したDataModelを好きに修正する

スクリーンショット 2022-03-05 10.20.07.png
👇今回はこのような形でEntityも新しく追加しました
スクリーンショット 2022-03-05 10.20.15.png

追加•修正したEntityのクラスを生成する

dataModelを選択してEditor -> Create NSManagedObject SubclassでEntityのクラスを自動生成する

その際追加したEntityのManual/Noneにするのをお忘れなく💡
スクリーンショット 2022-03-05 17.14.56.png

実装(結果必要なかった)

まずは現状のCoreDataManagerの中を確認

CoreDataManager
final class CoreDataManager: CoreDataProtocol {
    static let shared = CoreDataManager()
    
    private init() { }
    
    // アプリケーション内のオブジェクトとデータベースの間のやり取りを行う
    lazy var persistentContainer: NSPersistentCloudKitContainer = {
        let container = NSPersistentCloudKitContainer(name: "PadelLessonLog")
        container.loadPersistentStores(completionHandler: { _, error in
            if let error = error as NSError? {
                // リリースビルドでは通っても何も起きない
                assertionFailure("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    
    // persistentContainerデータベース情報を表す
    // 管理オブジェクトコンテキスト。NSManagedObject 群を管理するクラス
    var managerObjectContext: NSManagedObjectContext {
        persistentContainer.viewContext
    }
}

Core Dataをプロジェクト新規作成時のオプションで追加するとAppDelegateに一部コードが追加されますが、その部分をCoreDataManagerに移してあります。

Light Weight Migrationができるかの確認(※ やらなくてもよさそう)

まずは、マッピングモデルが生成できるか確認してみます。

CoreDataManager
    var checkLightWeightMigration: NSMappingModel {
        let subdirectory = "PadelLessonLog.momd"
        let sourceModel = NSManagedObjectModel(contentsOf: Bundle.main.url(forResource: "PadelLessonLog", withExtension: "mom", subdirectory: subdirectory)!)!
        let destinationModel = NSManagedObjectModel(contentsOf: Bundle.main.url(forResource: "PadelLessonLog 2", withExtension: "mom", subdirectory: subdirectory)!)!
        do {
            return try NSMappingModel.inferredMappingModel(forSourceModel: sourceModel, destinationModel: destinationModel)
        } catch {
            fatalError("migrationCheck error \(error)")
        }
    }

CoreDataManagerに👆を足して、適当な場所にprint(CoreDataManager.shared.checkLightWeightMigration)を書きデバッグで確認してみました。
スクリーンショット 2022-03-05 13.29.04.png
それっぽいものが表示されているので、Light Weight Migrationできそうなことが確認できました。

Light Weight Migrationの実装(※ 必要なかった)

まずは、現状(デフォルト)のNSMigratePersistentStoresAutomaticallyOptionNSInferMappingModelAutomaticallyOptionを確認してみようと思いました。

CoreDataManager
let container = NSPersistentCloudKitContainer(name: "PadelLessonLog")
print(container.persistentStoreDescriptions.count)
print(container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically)
print(container.persistentStoreDescriptions.first?.shouldInferMappingModelAutomatically)

ちょっと無理矢理ですがprintを仕込んでデバッグしてみます。
スクリーンショット 2022-03-05 14.53.59.png
どちらもすでにtrueで設定されていた❗️
どうやら修正の必要なくマイグレーションができそうです。

おわりに

長々と書きましたが、結果的にCoreDataのマイグレーションは、新しいデータモデルを作成・切り替えするだけで済みそうです👏
しかし、マイグレーションに失敗して大切なデータが消えてしまったら大問題なので、このあたりは整理して慎重に作業していきたいと思いました。

おまけ

こちらの記事に参考になりそうな部分がありました💡

Core Dataはまず、アプリのバンドル内で、永続ストアモデルから現在のバンドルモデルへマッピングするモデルを検索し、マイグレーションを実行しようとします。カスタムマッピングモデルが見つからない場合、Core Data は Lightweight マイグレーションを試みます。どちらの形式でも移行できない場合は、例外がスローされます。

参考

14
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10