【初心者向け】Core DataのLightWeightMigrationの方法【Swift4】


はじめに

エンジニア歴約3か月のまだまだ勉強中の身ですが、
同じSwift初心者向けにCore Dataで悩んだことを投稿します。
Qiita初投稿ですが、少しでもお力になれれば幸いです。

※この記事ではMacOS Mojave 10.14, Xcode 10.0, Swift4.2を使っています。

Core Dataとは?

ユーザーデータを永続的に保存するときに、UserDefaultsを利用している方も多いかと思いますが、
保存する項目が増えたり、複雑な処理を行う際には、SQLiteを用いてアプリ内のデータベースにデータを保存する方が便利です。
Core Dataはそれをシンプルなコードで実現するフレームワークです。

3rdParty製ライブラリであるRealmも有名ではありますが、
Apple純正のフレームワークということで利用しているiOSアプリも多くあると思います。

ここではCore Dataの具体的な使い方を説明はしませんが、
詳しい使い方等は他のQiita記事を参考にしてください。

(参考)【Swift3】ToDoアプリを作る【CoreData】

マイグレーションについて

一度データベースのmodelを作成した後、新しい機能追加などで属性を追加・削除したり、
エンティティをrenameしたりすることもあると思います。

ただし、一度作成したmodelをそのまま編集し、アプリを起動させるとCoreDataMigrationTestにてアプリが落ちてしまいます。
単純に起動させるだけであれば、アプリを一度アンインストールした後に再インストールを行うとアプリは起動しますが、既にapp storeにて配布している場合はユーザーに再インストールをお願いすることになるため、現実的ではありません。

そこでdatamodelに変更がある場合でも、アプリが落ちないようにするために、Core Dataのマイグレーションが必要になります。

本記事では、LightWeightMigrationの方法を説明します。
(他にもManual Migrationがあるそうです。)
一度、手を動かせばすぐに慣れるシンプルな方法でした!

公式ドキュメント

Appleに公式ドキュメントはこちらです。

Using Lightweight Migration

当然、英語のみのドキュメントで、面食らうこともありますが、
さらっと、

"set the renaming identifier to the name of the property in the source model"

と記載しているものでも、初心者にはなんのこっちゃ分からず、

さらには、


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)")
}

と記載してあるものの、Swift4でデフォルトで挿入される AppDelegate.swiftにそのまま利用することができなかったため、
全体的にハードルが高く、苦労しました。。。

以下、改めてできるだけわかりやすく説明します。

全体像(やること)

  1. やりたい変更がLightWeightMigrationが該当するかチェック
  2. 新しいDataModelの作成
  3. 利用するDataModelを作成した新しいModelに設定
  4. コードにてLightWeightMigrationを設定

1. やりたい変更がLightWeightMigrationが該当するかチェック

Apple公式ドキュメントによると、LightWeightMigrationが利用できる例は、以下の通りです。

- Attribute(属性)の追加
- Attribute(属性)の削除
- Attribute(属性)のオプショナルへの変更
- Attribute(属性)の非オプショナルへの変更(デフォルト値の設定あり)
- Entity(エンティティ)やAttribute(属性)の名称変更

他にも、試したところによるとType(型)変更も対応できるようですね。
上記に該当しなかったとしても、
簡易的な変更であればLightWeightMigrationが機能する可能性があるので、
一度やってみるのが良いかもしれません。
(そんなに時間はかかりませんので!)

2. 新しいDataModelの作成

Xcodeの左ペインで.xcdatamodeldファイルを選択し、
Xcodeの画面上部ツールバーから、
[Editor] -> [Add Model Version...] を選択してください。

適宜、新しいdatamodelファイルの名称を設定します。
(例: CoreDataTest_ver2.xcdatamodel)

Finishすると、作成した.xcdatamodelファイルが追加されています。

新規作成したdatamodelには以前のdatamodelのEntityがコピーされているので、
そこから必要な修正を加えてください。

画像例では、新しいAttribute "address" を追加しました。

3. 利用するDataModelを作成した新しいModelに設定

Xcodeの左ペインで新しく作成したdatamodelを選択し、
Xcode右ペインでFile Inspectorを選択してください。
(documentのようなアイコンの場所です)

[Core Data Model] -> [Model Version] -> [Current] の値を、
先ほど修正を加えた新しいdatamodelへ変更してください。

変更すると、Xcodeの左ペインでCurrentに設定したdatamodelに緑色のチェックマークが追加されます。

4. コードにてLightWeightMigrationを設定

新規Project立ち上げ時に、Use Core Dataにチェックを入れてProjectを作成した場合、
AppDelegate.swiftに以下のように、CoreDataを利用するためのプロパティが記載されています。

    // MARK: - Core Data stack

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "coreDataTest")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

ここのデフォルトで記載されてされているコードと、Apple公式ドキュメントとの関係性に悩まされたのですが、
Apple公式ドキュメントで記されている内容を紐解くと、要は以下の2つの設定が必要ということでした。

  • Migrationを自動で実行するOptionを"true"にする
  • MappingModelを自動で実行するOptionを"true"にする

これらの設定をどこで行うかというと、NSPersistentContainerpersistentStoreDescriptionsプロパティにありました。

description.shouldMigrateStoreAutomatically = true
description.shouldInferMappingModelAutomatically = true

名前も変わっていて、Apple公式ドキュメントからは辿りつけないですよね。。。

まとめると、デフォルトに記載されているコードに上記を追加すればOKです。

    // MARK: - Core Data stack

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "coreDataTest")

        // Using Lightweight Migration
        let description = NSPersistentStoreDescription()
        description.shouldMigrateStoreAutomatically = true
        description.shouldInferMappingModelAutomatically = true
        container.persistentStoreDescriptions = [description]

        container.loadPersistentStores(completionHandler: { _, error in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

あとはこちらでbuildして、何ごともなく起動できればOKです!
もちろん、app storeリリース前にはTestFlightを活用して、問題なくmigrationが実行できているか確認をしましょう!

以上、お付き合いいただきありがとうございました。