見出し画像

【徒然DB】気ままにUIKit-CoreData編コラム〜ちょっと気になってたのでConstraintsを検証してみた〜

概要

までで、CoreDataにConstraintsを付けた場合、FatalErrorが原因なので、
調べてみた。。。

⒈Use Core Dataチェックなしで、データモデルを追加する場合

//
//  AppDelegate.swift
//  TestNotUseCoreData
//
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return true
    }
    // MARK: UISceneSession Lifecycle
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    }
}

てな感じで、AppDelegateファイルにCoreData関連のメソッドがない👀

👉まあ、これはここまでの記事で想定の範囲内🕺

適当にデータモデルちゃんを追加して👀
てな感じでEntityとAttribute、Contraintsなんかを設定
てな感じで、ビューとコードを用意

のまとめコードをそのまま嵌め込み

ま、当たり前の話なんだけど、
persistentContainer
がないよのエラーになる👀

そこで、Use Core Dataにチェックを入れて作った新規プロジェクト

と、AppDelegate.swiftファイルのコードを比較すると、、、

//
//  AppDelegate.swift
//  TestNotUseCoreData
//

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return true
    }
    // MARK: UISceneSession Lifecycle
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    }
}
//
//  AppDelegate.swift
//  TestCoreData
//

import UIKit
import CoreData

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return true
    }
    // MARK: UISceneSession Lifecycle
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    }
    // MARK: - Core Data stack
    lazy var persistentContainer: NSPersistentCloudKitContainer = {
        let container = NSPersistentCloudKitContainer(name: "TestCoreData")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    // MARK: - Core Data Saving support
    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
}

とCoreData周りのコードが初期から追加されてる👀
じゃあ、

SceneDelegate.swift周りはどうなの?

ってことになると思うので、こちらも比較

//
//  SceneDelegate.swift
//  TestNotUseCoreData
//

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }
    func sceneDidDisconnect(_ scene: UIScene) {
    }
    func sceneDidBecomeActive(_ scene: UIScene) {
    }
    func sceneWillResignActive(_ scene: UIScene) {
    }
    func sceneWillEnterForeground(_ scene: UIScene) {
    }
    func sceneDidEnterBackground(_ scene: UIScene) {
    }
}
//
//  SceneDelegate.swift
//  TestCoreData
//

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }
    func sceneDidDisconnect(_ scene: UIScene) {
    }
    func sceneDidBecomeActive(_ scene: UIScene) {
    }
    func sceneWillResignActive(_ scene: UIScene) {
    }
    func sceneWillEnterForeground(_ scene: UIScene) {
    }
    func sceneDidEnterBackground(_ scene: UIScene) {
        (UIApplication.shared.delegate as? AppDelegate)?.saveContext()
    }
}

とほぼ、全く同一ってことに気づくよね👀
なので、

CoreDataをインポートしてあげて、、、
てな感じで、CoreDataを使うコードを貼り付けて、

コンテナ名が、

let container = NSPersistentCloudKitContainer(name: "TestCoreData")

のままになっているので、

let container = NSPersistentCloudKitContainer(name: "Model")

て感じで、該当するモデル名に変更して、

念の為、一旦フォルダをクリーン
Cleanをクリック

プロジェクトファイル自体を開き直すと、

てな感じでエラーは解消されてる
Constraintsにnameを追加してるのに、
シミュレーターを実行してもエラーは起きない👀

⒉Use Core Dataを最初からチェックした場合

Constraintsにnameを追加しても、、、
普通に動くね👀

ちなみに、、、

で動かなかったコードを追加して動くか検証

てな感じで、SubClassを追加してあげて〜〜〜

今回のコード(Constraints)

class IndexesCoreDataViewController: UIViewController, UITextFieldDelegate{

    @IBOutlet weak var myTextField: UITextField!
    @IBOutlet weak var myLabel: UILabel!
    //管理オブジェクトコンテキスト
    var managedContext:NSManagedObjectContext!
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        do {
            //管理オブジェクトコンテキストを取得する。
            let applicationDelegate = UIApplication.shared.delegate as! AppDelegate
            managedContext = applicationDelegate.persistentContainer.viewContext
            //管理オブジェクトコンテキストからPlayerエンティティを取得する。
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
            let result = try managedContext.fetch(fetchRequest) as! [Player]
            //すべてのPlayerエンティティの名前をラベルに表示する。
            for data in result {
                myLabel.text = myLabel.text! + "," + data.name!
            }
            //デリゲート先に自分を設定する。
            myTextField.delegate = self
        } catch {
            //エラーメッセージをアラートで表示する。
            let alert = UIAlertController(
                title: "エラー",
                message: String(describing: error),
                preferredStyle: UIAlertController.Style.alert
            )
            self.present(alert, animated:true, completion:nil)
        }
    }
    //Returnキー押下時の呼び出しメソッド
    func textFieldShouldReturn(_ textField:UITextField) -> Bool {
        do {
            //ラベルの値にテキストフィールドの値を追記する。
            myLabel.text = myLabel.text! + "," + myTextField.text!
            //新しいPlayerエンティティを管理オブジェクトコンテキストに格納する。
            let player = NSEntityDescription.insertNewObject(forEntityName: "Player", into: managedContext) as! Player
            //Playerエンティティの名前にテキストフィールドの値を設定する。
            player.name = myTextField.text
            //管理オブジェクトコンテキストの中身を永続化する。
            try managedContext.save()
            //キーボードをしまう
            self.view.endEditing(true)
        } catch {
            //エラーメッセージをアラートで表示する。
            let alert = UIAlertController(
                title: "エラー",
                message: String(describing: error),
                preferredStyle: UIAlertController.Style.alert
            )
            self.present(alert, animated:true, completion:nil)
        }
        return true
    }
}

を組み込み〜〜〜〜

で普通に動く👀
1回目の佐藤をデータベースに追加後〜〜〜
2回目に佐藤を追加しようとするとコードどおりにエラー発生🕺

さらにちなみに、

⒈でやったUse Core Dataにチェックなしで作ったプロジェクトで

CodegenをManualにしているにも関わらず、、、
クラスとプロパティを追加すると、、、
てな感じで、なぜかエラーが発生することがわかる👀

まとめ

以上から、これまでの記事で書いたとおりなんだけど、

⒈最初からUse Core Dataにチェックを入れて作っておいた方がいい。
👉後からデータモデルを追加出来なくはないが、どんな影響があるかわからない(=サイト記事当初からPersistence Containerに仕様が変わっていたりな)ので。

⒉Constraints=一意制約は、データベースの初期段階で使うなら追加する。
👉データベース全体に対する制約になるので、後から追加しようとして、既に制約に反するデータがモデル上に存在する場合、制約違反=FatalErrorになるので。

⒊100の理論や知識よりも、1の検証(実際に動かして確かめる)が大事。思い込まずに動かして確認する。
👉プログラミングにしろデータベースにしろ、機能やロジックは頭では理解してる(インターネットや本なんかに転がってるからね)と、いざ動かしてみると、想定の範囲外ってことはよくある話。

ってことがよくわかると思う。こんなことを書くと、経験狂信者の人が、

だから経験が大事だ

って言いそうなんだけど、
こうやってWEB記事で公開しても、自分の経験ではないことや、興味ないことは、経験狂信者の人ほど、

今の自分には関係ない

って他人の経験(=知識や理論)を聞こうとしない。

一番大事なのは、

💃経験したことを自分だけにとどめておくのではなく、
きちんと経験から得た知識を発信すること🕺

ここからは、余談なんだけど、

SQLとかACCESSなんかでも、Constraintsはあるけど、
制約に反するものに関しては入力を許さないなど強固なデータベースを構築する際には非常に有用。
ただ、裏を返すと後からDBの仕様変更やリレーションの変更なんかがあった場合、Constraintsが逆に邪魔をして、柔軟性がなくなる
=要求変更に対応できなくなるか、改修に余計な手間がかかったり、下手したら0から作り直した方が早いなんてことはよくある。

データベースは所詮、データを格納するテーブルの構成に過ぎず、
格納されたデータ自体を新しいテーブルに取り込みなおした方が早い

んだけどね。
そういうことに気づかずに、初心者っていうか、管理管理、安全安全ってそっちばかりに気がいってる自称、データベースエンジニアなんかで、教科書とか学校で習ったことばかりを実践しようとして、

いきなり最初からガッチガチにConstraintsを追加したがる

し、要求変更があった場合、他のDBを作り直した方が早いことでも、

それをせずに直せるのがエンジニアだ!!!

みたいな感じで、却って余計な時間ばかりかけて悦に浸ってるw🤣

の【開発哲学】すら一切読まずに、読めば数日〜数ヶ月で理解できたり改善できたりすることに数年〜十数年とか時間をかけてるからね👀

💃やり方はひとつではないのに💦🕺

知識として、テーブル、クエリ、フォーム、レポート、ビューを

頭に入れてるだけで、

  • リレーショナルデータベース(RDB)

  • CRUD(Create、Read、Update、Delete)

  • ACID(Atomically、Consistency、Isolation、Durability)

を概念としてすら肌感覚で身に付けてないからね。。。データベースなんて後は所詮、テーブル内にあるデータに対して、

アクセスみたいに画面上の操作でも出来るようにするか
SQLみたいにコードでやるか

だけの違いなんだけどね、、、💦🤔

CoreDataがまさに、

データモデルの中にEntityってテーブルを作って、
Attributeって各データ項目を扱ってるだけ
👉データベースでしょ?👀

それを色々な各設定項目なんかをXcodeみたいなGUIやコードを併用しながら、やりたいことをやってるだけ🕺

まだ殆ど記事にはしてないけど、実は、既にSwiftUIも

てな感じで、700個以上💦

サンプルもまとめてひとつに繋ぎ終わってるし、あとは次いでに

を年内に勉強しよかなあくらいなもんなんで〜〜〜!

てことで今日はもう疲れたし、

本編はまた明日以降で気が向いたらまた書こ!!!
残り15記事くらいだし。
なんかGW明けくらいには終わってそうだね👀

この記事が気に入ったらサポートをしてみませんか?