[CoreData][SwiftUI]CoreDataを使ったアプリその1(生成削除篇)@Xcode11/Swift5

Xcode11.4/MacOS10.15/iOS13.4でのCoreDataの最新の使い方を説明します

環境

Xcode11.4, MacOS10.15を使います。

Swift5を使って、最新の記述方法で作っていきます。

プロジェクト作成

macOSのAppを選んで、

CoreDataProject setup

Swift/SwiftUI/CoreDataを選んで、

#alttext

プロジェクト作成は、終わり。

CoreData関連のファイルは、後で、Xcodeからコード生成することにもなるので、別グループを作って管理します。

CoreDataGroup

プロジェクト設定の確認

生成されているAppDelegate.swiftのコードを確認すると、以下のようなコードがあります。

コード

func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
    // Add `@Environment(\.managedObjectContext)` in the views that will need the context.
    let contentView = ContentView().environment(\.managedObjectContext, persistentContainer.viewContext)

    // Create the window and set the content view. 
    window = NSWindow(
        contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
        styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
        backing: .buffered, defer: false)
    window.center()
    window.setFrameAutosaveName("Main Window")
    window.contentView = NSHostingView(rootView: contentView)
    window.makeKeyAndOrderFront(nil)
}
ここでCoreDataの操作に必要となるNSManagedObjectContextをContentViewのenvironmentに設定していますので、View側からは、@Environmentを使用して参照することができます。

サンプル用の要素作成

作成したアトリビュートに、特に意味はないですが、以下のような要素を作ります。

CoreDataEntity

SwiftUI向け設定

ManagedObjectContextへのアクセスやFetchを用意しておきます。

コード

@Environment(\.managedObjectContext) var moc
@FetchRequest(entity: AppEntity.entity(), sortDescriptors: [] ) var entities: FetchedResults
注意
AppDelegate40行目付近に、以下のコードがあると思いますが、lazyキーワードを削除しないとアプリがクラッシュします。
コード

lazy var persistentContainer: NSPersistentContainer = {
おそらく、評価するタミングの問題ですが、persistentContainerが評価される前に、@FetchRequestが評価されてしまい、クラッシュするようです。@FetchRequestが評価される前に、persistentContainerが評価されれば問題ないのですが、そのようなタイミングで評価するように変更するのは手間なので、lazyキーワードを削除するのが簡単です。

CoreDataアトリビュートWrapper作成

CoreDataエンティティにアクセスしやすいように、Wrapperを作っておきます。String等のAttributeは、Attributeの設定で非Optionalにしても、変数としては、String?等のOptional型になってしまうため、常にUnwrapする必要が発生してしまい、記述が煩雑になってしまいます。特にSwiftUIとは相性が良くないと思いますので、このようなwrapperを作っておくと便利です。

コード

extension AppEntity {
    var strAttr:String {
        get {
            return stringAttr ?? ""
        }
        set(newValue) {
            stringAttr = newValue
        }
    }
    var id: String {
        get {
            let uuid = uuidAttr ?? UUID()
            uuidAttr = uuid
            return uuid.uuidString
        }
    }
}

簡単なUIの作成

CoreData操作のためのUIを作っていきます。
まずは、CoreData内に存在する要素を表示するリスト

コード

VStack {
    Text("num of AppEntity  \(entities.count)")
    List(entities, id:\.self) { entity in
        Text("UUID: \(entity.id)  String: \(entity.strAttr)  Int64: \(entity.int64Attr)")
    }

}

作成/削除するためのボタン(とそこから呼ばれる関数)

ボタン

VStack  {
    Button(action: {self.createNewEntity()},
           label: {Text("Create a new Entity")}
    )
    Button(action: {self.deleteEntities()},
           label: {Text("Delete selected Entities")}
    )
}
ボタン押下時に呼ばれる関数

func createNewEntity() {
    // Entity作成 
}
func deleteEntities() {
    // Entity 削除
}

ここまでのコードをまとめると以下のようになります。

コード

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: AppEntity.entity(), sortDescriptors: [] ) var entities: FetchedResults
    @State private var selections = Set()

    var body: some View {
        HStack {
            VStack {
                Text("num of AppEntity  \(entities.count)")
                List(entities, id: \.self, selection: $selections) { entity in
                    Text("id: \(entity.id) int64: \(entity.int64Attr) string: \(entity.strAttr) ")
                }
            }
            VStack  {
                Button(action: {self.createNewEntity()},
                       label: {Text("Create a new Entity")}
                )
                Button(action: {self.deleteEntities()},
                       label: {Text("Delete selected Entities")}
                )
            }
        }
    }
    func createNewEntity() {
        // Entity作成 
    }
    func deleteEntities() {
        // Entity削除
    }
}

この段階で機能は動きませんが、実行するとウィンドウが表示されます。




CoreDataでのEntity生成

以前は、insertNewObject等を使っていましたが、いまでは非常に簡単にEntityを作成できます。

コード

let newEntity = AppEntity(context: moc)

CoreDataは、複数のContextを並行して操作することができるので、どのContextに追加するかを指定する必要がありますが、それだけでOKです。
作成した後は、Attributeを設定しておけばokです。
先の // Entity作成 の箇所のコードは以下のようになります。

AppEntity生成

func createNewEntity() {
    let newEntity = AppEntity(context: moc)
    newEntity.uuidAttr = UUID()
    newEntity.stringAttr = Date().description
    newEntity.int64Attr = Int64.random(in: 0...256)
}
UUIDは、そのままUUIDを設定していますが、Stringは、あまり重複しないであろう文字列ということで現在日時を文字列化したものを設定しています。Int64には、乱数をセットしています。

CoreDataでのEntity削除

Entityを削除するためのコードは、以下です。

コード

moc.delete(entity)
Batchで削除(メモリ上に展開せずに削除)する時は、NSBatchDeleteRequestが使えますが、今回は普通に削除します。

選択されたEntityがselectionsに保持されていますので、それを削除して、終わりです。

コード

func deleteEntities() {
    for entity in selections {
        moc.delete(entity)
    }
    selections = []
}

CoreDataでの要素の作成削除

ここまでのコードをまとめると、以下になります。

コード

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: AppEntity.entity(), sortDescriptors: [] ) var entities: FetchedResults
    @State private var selections = Set()

    var body: some View {
        HStack {
            VStack {
                Text("num of AppEntity  \(entities.count)")
                List(entities, id: \.self, selection: $selections) { entity in
                    Text("id: \(entity.id) int64: \(entity.int64Attr) string: \(entity.strAttr) ")
                }
            }
            VStack  {
                Button(action: {self.createNewEntity()},
                       label: {Text("Create a new Entity")}
                )
                Button(action: {self.deleteEntities()},
                       label: {Text("Delete selected Entities")}
                )
            }
        }
    }
    
    func createNewEntity() {
        let newEntity = AppEntity(context: moc)
        newEntity.uuidAttr = UUID()
        newEntity.stringAttr = Date().description
        newEntity.int64Attr = Int64.random(in: 0...256)
    }
    
    func deleteEntities() {
        for entity in selections {
            moc.delete(entity)
        }
        selections = []
    }
}

実行すると以下のようになります。

RunningApp

"Create a new Entity"ボタンを押すと、新しいEntityが生成されます。
"Delete selected Entities”を押すと、選択されている要素が削除されます。

注意
繰り返しですが、AppDelegate 40行目付近のlazyを削除しないとクラッシュします。




CoreData とアーキテクチャ

CoreData をお試して使う分には、Xcode が生成してくれるコードを使って試してみるのが良いと思います。

少し凝った機能を実装しようとすると、適切なアーキテクチャを選択しておくことが重要となります。

MVVM での実装例を説明していますので、以下の記事も参考にしてください。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

# SwiftUI2.0 になって、少し古くなってしまいましたが、定番本です。

CoreData おすすめ本

系統だって理解するには、以下の本がおすすめです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です