プログラムでCore Dataをセットアップする without xcdatamodel

Core Dataを使おうと思って調べると、解説のほとんどが .xcdatamodeld ファイルでセットアップを始める。
.xcdatamodeld はグラフィカルに構造を表示し、便利な反面、裏側の仕組みを理解しづらい、GitHubでレビューしづらいなどのコストは無視できない。

さらに近年問題になり得るのは、Swift PlaygroundsでiOS/iPadOS/macOS向けアプリを作成する環境整備が進んでいることだ。
Macだけでなく、iPadでも手軽にアプリ開発を進めることができるようになるが、Swift Playgroundsでは .xcdatamodel を取り扱うことができない。*1

ということで、ここでは .xcdatamodeld ファイルで始めたプロジェクトを、プログラマブルに置き換える。

ちなみに、.xcdatamodel を複数束ねたものが、.xcdatamodeld

cocoa.hatenablog.com

.xcdatamodeld で定義した状態の確認

まずは置き換え元になる .xcdatamodeld 入りプロジェクトを作る。

プロジェクト作成 with xcdatamodeld

Create a new Xcode project からスタート

"Use Core Data" をチェックして、プロジェクトを作成

.xcdatamodeld がProjectツリーにある

xcdatamodeld の状態

生成されるコード

初期状態だと、Itemは "Class Definition" として、コード生成されている。
ContentView の中で参照されている item.timestamp をクリックすると、生成されたコードを見ることができる。

import Foundation
import CoreData


extension Item {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Item> {
        return NSFetchRequest<Item>(entityName: "Item")
    }

    @NSManaged public var timestamp: Date?

}

extension Item : Identifiable {

}

確認用のデータを入れる

この後の変更で振る舞いが変わっていないかを確認するために、アプリを実行して、データを入れておくとよいだろう。

プロジェクト作成の状態で、タイムスタンプを記録するアプリが動く

Itemをプログラムとして再定義

型定義

コード生成を参考に、自分で定義する。

public class Item: NSManagedObject {
    @NSManaged public var timestamp: Date?
}

extension Item : Identifiable {}

Entity情報

Item型のEntityとしての情報を定義する。

extension Item {
    static var entityDescription: NSEntityDescription {
        let entity = NSEntityDescription()
        entity.name = String(describing: Self.self)
        entity.managedObjectClassName = NSStringFromClass(Self.self)

        entity.properties = [
            {
                $0.name = "timestamp"
                $0.attributeType = .dateAttributeType
                $0.isOptional = true
                return $0
            }(NSAttributeDescription()),
        ]
        return entity
    }
}

NSPersistentContainer初期化時にNSManagedObjectModelを指定

NSManagedObjectModelのEntityにItemNSEntityDescriptionを渡すことで、.xcdatamodeld ファイルがある時と同じ状態のContainerとして初期化する。

let managedObjectModel = NSManagedObjectModel()
managedObjectModel.entities = [
    Item.entityDescription,
]
container = NSPersistentContainer(name: "CoreDataExample",
                                  managedObjectModel: managedObjectModel)

余談だが、nameのみのイニシャライザはnameに渡した文字列で .xcdatamodeldファイルを探しているだけなので、ファイル名を変えるか、nemeの指定文字列を変えると初期化に失敗する。

[error] error: Failed to load model named CoreDataExamples

.xcdatamodeld ファイルを削除

ファイルがあると (正確にはItem定義があると) 生成されるコードと、定義したItemが衝突するので、ファイルを削除しておく。

置き換えの確認

アプリの実行して、変更前のデータを表示できていることを確認。

xcdatamodeld 定義で入れていたデータを表示できる

データ操作もできている。

データの追加、削除も、もちろんできる

To Be Continued

IndexやRelationship、Migrationといった複雑な設定はまた今度

*1:2022/07/14現在