コードの整理(CoreData 手習い番外編)

CoreData

はじめに

今回は手習い番外編です。
ここまで書き散らしてきたソースを整理します。少々迷走しています。ご承知おきください。

目標

今回の目標は、

  • View(SwiftUI)関係のコードとCoreDataを取り扱うコードを分離する
  • AppDelegate.swiftSceneDelegate.swiftからCoreDataを取り扱うコードを分離する
  • 分離したCoreDataを取り扱うコードを汎用的にする(使い回ししたい)

ことです。

CoreDataを操作するメソッドを切り分けします

これまでに作ってきたCoreDataを操作するメソッドは、

  • データを取り出す
  • データを追加する
  • データを更新する
  • データを削除する

の4つです。
これらは今、下記のような状態になっています。

struct ContentView: View {
    @State var array:[String] = ["りんご", "みかん", "バナナ", "メロン", "ぶどう", "かき", "いちご"]
    var body: some View {
        ここでボタンを4つ並べてそれぞれでfetchData、insertData、deleteData、updateDataを呼び出しています
        データは`array`に入れられて、Listビューに表示しています。
    }
    func delete(offset:IndexSet)
    {
        array.remove(atOffsets: offset)
    }
    func fetchData()
    {
        〜略〜
    }
    func insertData()
    {
        〜略〜
    }
    func deleteData()
    {
        〜略〜
    }
    func updateData()
    {
        〜略〜
    }
}

なので、別クラスとして外に出してしまいましょう。

クラス名はCoreDataCRUDとしました。若干ダセぇ気がします。このへんのセンスを身に付けたいところ。

外に出すと言っても、そのままコピーするだけです。

ところが、切り出したところ、データを取り出すメソッドでエラーが出ました。
self.arrayのところですね。これはもともと取り出したデータを構造体(SwiftUI)のフィールドにそのまんま放り込んでいたためです。
エラーが出たままでは進めづらいので、少し変更します。

func fetchData() -> [WorkerEntity] //変更 戻り値を付けました
    {
        〜略〜
        var workers:[WorkerEntity] = [] //追加
        do {
            workers = try context.fetch(workersFetch) as! [WorkerEntity] //変更
//            self.array.removeAll() //ここで行わない
            for worker in workers {
//                self.array.append(worker.name ?? "") //削除
                print("Fetched Worker" + (worker.name ?? ""))
            }
//            print("Fetched Data:" + self.array.count.description) //ここでは行わない
        } catch let error as NSError {
            print(error)
        }
        return workers //追加
    }

あくまでもこのメソッドのお仕事はデータを取り出して渡すだけです。ですので、渡す先(ここではself.array)のことは他所でやっていただきます。このメソッドを呼び出すところとかね。

切り出したメソッドを呼ぶように元コードを修正する

早速新しく作ったクラスを呼び出すように変更していきます。
fetchDataがちゃんと動くかが心配ですが、まぁうまくいかなければその時直しましょう。

基本的にself.~Dataself.coredataCRUD.~Dataにするだけです。
あ、coredataCRUDは新しく作ったCoreDataCRUDクラスのインスタンスです。

fetchDataだけは切り出したときにいろいろいじったので、ちょっと一捻り必要です。ボタンを押したときにArrayの中をきれいに消して、取得したデータの中から名前の配列を作ります。

self.array.removeAll()
let workers = self.coredataCRUD.fetchData()
for worker in workers {
    self.array.append(worker.name ?? "")
}
print("Fetched Data:" + self.array.count.description)

なんなら、名前だけを配列にまとめて渡すメソッドをCoreDataCRUDクラスにもたせておいても良いかもしれません。

この時点で動かしてみますと、これまでの手習いで試した動きが再現できました。もともと有ったメソッドはもう要らないので、さっぱりと消してしまいましょう。

余談

さっぱり消しましたが、お好みによりGitやSVNなどで管理をしたほうが良いです。

ファイルも別にしてしまいましょう

さてと、切り出しができましたが、Viewを司るコードと同居しているのはいかがなものか?
ということで、追い出します。

新しくSwiftファイルを作ってまるっと移してしまいます。

元のファイルではもうCoreDataを使いませんので、import CoreDataは削除して大丈夫です。
代わりに新しいSwiftファイルにて追加しておきます。

それと、UIApplicationunresolved identifierと言われました。
import UIKitを追記すると解消できますが、果たしてそれで良いのかというところですね。

このクラスはDBからデータを取り出したり更新したりするもので、User Interfaceをどうのこうのすることを目的にしていないです。
可能ならば、import UIKitは追記したくないなと思うわけです。

AppDelegate.swiftからコードを移してくる

UIApplicationunresolved identifierと言われた箇所は、AppDelegate.swiftpersistentContainersaveContextを呼び出しているところです。

これらをそのまま新しいSwiftファイルに持ってきましょう。

移すとそれに付随してSceneDelegate.swiftでもエラーが出ます。
これはAppDelegate.swiftpersistentContainersaveContextを使っていたため「そんなメソッドなんぞ見つからねぇ」と騒いでいるだけです。

新しく作ったクラスのインスタンスを作って呼び出させましょう。

チョット待って!

Appleがお膳立てしたものをいじってしまって大丈夫なのか?少々の不安の種ができたので試しに動かしてみます。

ドキドキしながら実行したところ、これまでの手習いで試した動きを無事再現できました。

まとめ

これでいわゆるビューとロジックの(少々粗っぽい)分離ができました。

あとは、メソッド内に直書きしているエンティティや条件(prediction)の生成などをメソッド外に出してみましょうかね。

今のままでは、検索条件などが変わるたびに似たようなメソッドが増殖していって見通しが悪くなってしまいます。

それから、もう一歩踏み込んで汎用的に使えるようにしてみたいですね。
CoreDataCRUDクラス内に具体的なエンティティの型の情報が無い状態にしたいのです。

コードの見直しや整理はなかなか落ち着いてできることではないので、つい熱が入ってしまい脱線しそうな予感がします。この件は手習いとは別に取り組んでみたいと思います。