LoginSignup
0

More than 5 years have passed since last update.

CoreDataのCascade deleteの挙動の検証

Last updated at Posted at 2016-12-11

以前、CoreDataで、配列情報を持つ必要があったので、

CoreDataのCascade deleteのサンプルを1日で作って、挙動を検証してみました。

Cascade Deleteなのに、Cascade Delegateって名前間違えましたw

完全に疲れてますねw

ちなみにRealmは、Cascade Deleteがまだ、サポートされてないので、

以前、Realmを使っていた時は、自分で自作して子テーブルまで消してました。

それに対し、CoreDataは、Cascade Deleteをサポートしています。LimitもOffsetもあります。

Cascade Deleteとは、Wikipediaによると

参照される側の関係変数の組が削除された場合、参照する側の関係変数の対応するすべての組は削除される。
同様に、参照される側の関係変数の組が更新された場合、参照する側の関係変数の外部キーの値は同じ値に更新される。

なるほど、わかりにくい

ちょっと図で具体的に説明してみます。

スクリーンショット 2016-12-10 23.36.16.png

SearchConditionは、検索条件テーブル

WorkTyleは、検索条件の働き方の複数選択を入れるテーブルです。

検索条件に、働き方を複数選択するアプリがあったとします。

この場合の働き方は、「アルバイトや、パート、正社員、契約社員、その他」を選択できるとします。

最初の画面で指定なしを選択すると
スクリーンショット 2016-12-10 23.56.20.png

次の画面に行って、、「パート、契約社員、その他」を選択して、決定を押すと
スクリーンショット 2016-12-10 23.57.06.png

変更されて
スクリーンショット 2016-12-10 23.54.32.png

もう一度条件を選択して変更すると
スクリーンショット 2016-12-10 23.54.59.png

選択した順番通りに、条件が変更される
スクリーンショット 2016-12-10 23.54.57.png

このサンプルで、Casdate Deleteが行われているが確かめます。

CoreDataは、SQLiteに保存するので、検索条件のSearchConditionの1つのデーブルだけで、配列情報を持つことができません。

そこで、One to Manyの関係のリレーショナルなWorkStyleというテーブルを作ります。

リレーショナルな関係を作るには、Add Entityで、SearchConditionテーブルとWorkStyleテーブルを作った後に、

Editor Styleを、GUI形式にして、SearchConditionテーブルを、Controlキーを押しながら、WorkStyleに接続すると、矢印がつながります。

その後以下の図のように、テーブルのカラム名や、プロパティー名を追加したり、変えていきます。

スクリーンショット 2016-12-10 23.46.59.png

スクリーンショット 2016-12-10 23.48.17.png

WorkStyleテーブルが、Nullifyから、Cascade、To Oneから、To Manyに変わっていることことに注意してください。

Model.xcdatamodeldを選択して、メニューバーのEditorから、Create NSManagedObject SubClassを選択すると、

Entityに対応するNSManagedObject SubClassが自動生成されます。

自動生成するときに、フォルダと、グループを指定し忘れると、違うフォルダとグループに自動生成されるので注意してください。

後、自動生成されたファイルのクラスと、自動生成する前に同じクラス名が存在するとエラーになるので、クラス名が衝突しないように気をつけてください。

スクリーンショット 2016-12-10 19.51.16.png

Cascade Deleteで、僕がハマったのは、

親テーブルのSearchConditionで、働き方を複数持つためのRelationshipsのworkStyleSetに、nilを入れて上書き保存して削除しても

子テーブルのWorkStyleレコードは、外部キーが消えるだけで、全部削除されないで残るということです。

自分が検証したところ、子テーブルのWorkStyleにRelationshipsのある、親テーブルのレコードを削除しないと、

子テーブルのレコードが全部削除されないということがわかりました。

そこで、以下の実装で、解決しました。

主な実装は以下の通りです。

SearchConditionViewController.swift
import UIKit

class SearchConditionViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    var searchConditionArray = [SearchCondition]()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationItem.title = "条件選択"

        // UITableViewDelegate
        self.tableView.delegate = self
        // UITableViewDataSource
        self.tableView.dataSource = self

        self.tableView.register(
            UINib(
                nibName : SelectWorkStyleTableViewCell.className,
                bundle : nil
            ),
            forCellReuseIdentifier: SelectWorkStyleTableViewCell.className
        )

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        searchConditionArray = SearchConditionManager.sharedManager.fetchAll() ?? [SearchCondition]()

        self.tableView.reloadData()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

extension SearchConditionViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if let tableViewCell = self.tableView.cellForRow(at: indexPath),
           tableViewCell.className == SelectWorkStyleTableViewCell.className {

            let workStyleStoryboard = UIStoryboard(name: Constraints.WorkStyleStoryboard.rawValue, bundle: nil)

            guard let selectWorkStyleViewController = workStyleStoryboard.instantiateInitialViewController() as? SelectWorkStyleViewController else {
                // send error report
                return
            }

            if searchConditionArray.count > 0 {
                let searchCondition = self.searchConditionArray[indexPath.row]

                if let workStyleSet = searchCondition.workStyleSet,
                    let workStyleArray = workStyleSet.array as? [WorkStyle] {

                    var selectedWorkStyleArray = [String]()

                    for workStyle in workStyleArray {
                        selectedWorkStyleArray.append(workStyle.workStyleType ?? "")
                    }

                    selectWorkStyleViewController.selectedWorkStyleArray = selectedWorkStyleArray

                }
            }

            self.navigationController?.pushViewController(selectWorkStyleViewController, animated: true)
        }
    }
}

extension SearchConditionViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if searchConditionArray.count == 0 {
            return 1
        } else {
            return searchConditionArray.count
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        guard let selectWorkStyleTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: SelectWorkStyleTableViewCell.className) as? SelectWorkStyleTableViewCell else {
            return ViewUtlity.getTableViewCell(color: UIColor.clear)
        }

        if searchConditionArray.count == 0 {
            selectWorkStyleTableViewCell.workStyleLabel.text = "指定なし"
        } else {
            let searchCondition = self.searchConditionArray[indexPath.row]

            if let workStyleSet = searchCondition.workStyleSet,
                let workStyleArray = workStyleSet.array as? [WorkStyle] {
                var workStyleString = ""

                for (index, workStyle) in workStyleArray.enumerated() {
                    if let workStyleType = workStyle.workStyleType,
                        workStyleType.isEmpty == false {
                        if index == 0 {
                            workStyleString += workStyleType
                        } else {
                            workStyleString += "," + workStyleType
                        }
                    }
                }

                print(workStyleString)

                if workStyleString.isEmpty == true {
                    selectWorkStyleTableViewCell.workStyleLabel.text = "指定なし"
                } else {
                    selectWorkStyleTableViewCell.workStyleLabel.text = workStyleString
                }

            }
        }

        return selectWorkStyleTableViewCell
    }
}
SelectWorkStyleViewController.swift
import UIKit

class SelectWorkStyleViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    var workStyleTypeArray = [String]()

    var selectedWorkStyleArray = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        workStyleTypeArray.append(WorkStyleType.Arbeit.rawValue)
        workStyleTypeArray.append(WorkStyleType.Part.rawValue)
        workStyleTypeArray.append(WorkStyleType.RegularEmployee.rawValue)
        workStyleTypeArray.append(WorkStyleType.ContractEmployee.rawValue)
        workStyleTypeArray.append(WorkStyleType.TemporaryStaff.rawValue)
        workStyleTypeArray.append(WorkStyleType.Other.rawValue)

        // UITableViewDelegate
        self.tableView.delegate = self
        // UITableViewDataSource
        self.tableView.dataSource = self

        self.tableView.register(
            UINib(nibName : WorkStyleTableViewCell.className, bundle : nil),
            forCellReuseIdentifier: WorkStyleTableViewCell.className
        )

        self.tableView.reloadData()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    @IBAction func tapDesideButton(_ sender: Any) {
        if let searchCondition = SearchConditionManager.sharedManager.create() {
            var workStyleArray = [WorkStyle]()

            for workStyleType in self.workStyleTypeArray {
                if self.selectedWorkStyleArray.contains(workStyleType) {
                    if let workStyle = WorkStyleManager.sharedManager.create() {
                        workStyle.workStyleType = workStyleType
                        workStyleArray.append(workStyle)
                    }
                }
            }

            searchCondition.workStyleSet = NSOrderedSet(array: workStyleArray)

            SearchConditionManager.sharedManager.update(updateSearchCondition: searchCondition)
        }

        let _ = self.navigationController?.popViewController(animated: true)
    }
}

extension SelectWorkStyleViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let tableViewCell = self.tableView.cellForRow(at: indexPath)

        if let workStyleTableViewCell = tableViewCell as? WorkStyleTableViewCell  {
            if workStyleTableViewCell.isWorkStyleTableViewCellSelected {
                // すでに選択されていた時
                workStyleTableViewCell.isWorkStyleTableViewCellSelected = false
            } else {
                // 未選択の時
                workStyleTableViewCell.isWorkStyleTableViewCellSelected = true
            }

            if let workStyleLabelText = workStyleTableViewCell.workStyleLabel.text {
                if selectedWorkStyleArray.contains(workStyleLabelText) {
                    // すでに選択されていた時
                    let newSelectedWorkStyleArray = selectedWorkStyleArray.filter({ (workStyle: String) -> Bool in
                        if workStyle == workStyleLabelText {
                            return false
                        } else {
                            return true
                        }
                   })

                    self.selectedWorkStyleArray = newSelectedWorkStyleArray
                } else {
                    self.selectedWorkStyleArray.append(workStyleLabelText)
                }
            }
        }

        self.tableView.reloadData()
    }
}

extension SelectWorkStyleViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.workStyleTypeArray.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let workStyleTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: WorkStyleTableViewCell.className) as? WorkStyleTableViewCell else {
            return ViewUtlity.getTableViewCell(color: UIColor.clear)
        }

        let workStyleType = workStyleTypeArray[indexPath.row]

        if self.selectedWorkStyleArray.contains(workStyleType) {
            workStyleTableViewCell.isWorkStyleTableViewCellSelected = true
        } else {
            workStyleTableViewCell.isWorkStyleTableViewCellSelected = false
        }

        workStyleTableViewCell.workStyleLabel.text = workStyleType

        return workStyleTableViewCell
    }
}
SearchConditionManager.swift
import UIKit
import CoreData

class SearchConditionManager: NSObject {

    static let sharedManager = SearchConditionManager()

    private override init() {

    }

    func create() -> SearchCondition? {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            // FIXME: send error report
            return nil
        }

        let viewContext = appDelegate.persistentContainer.viewContext

        guard let searchConditionEntity = NSEntityDescription.entity(forEntityName: SearchCondition.className, in: viewContext) else {
            // FIXME: send error report
            return nil
        }

        guard let searchCondition = NSManagedObject(entity: searchConditionEntity, insertInto: viewContext) as? SearchCondition else {
            return nil
        }

        do {
            try viewContext.save()
            return searchCondition
        } catch let error as NSError {
            // FIXME: send error report
            print("error.description = \(error.description)")
            return nil
        }
    }

    func fetchAll() -> [SearchCondition]? {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            // FIXME: send error report
            return nil
        }

        let viewContext = appDelegate.persistentContainer.viewContext

        let fetchRequest: NSFetchRequest<SearchCondition> = SearchCondition.fetchRequest()

        //ascendind:true 昇順、false 降順です
        let sortDescripter = NSSortDescriptor(key: "updateTime", ascending: false)
        fetchRequest.sortDescriptors = [sortDescripter]

        do {
            return try viewContext.fetch(fetchRequest)

        } catch let error as NSError {
            // FIXME: send error report
            print("error.description = \(error.description)")
            return nil
        }
    }

    func update(updateSearchCondition: SearchCondition) {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            // FIXME: send error report
            return
        }

        let viewContext = appDelegate.persistentContainer.viewContext

        if let searchCondition = self.create() {
            searchCondition.workStyleSet = updateSearchCondition.workStyleSet
            searchCondition.updateTime = NSDate()
        }

        do {
            try viewContext.save()


            if let searchConditionArray = self.fetchAll(),
                searchConditionArray.count >= 2 {

                print("start")
                for searchCondition in searchConditionArray {
                    if let workStyleSet = searchCondition.workStyleSet,
                        let workStyleArray = workStyleSet.array as? [WorkStyle] {
                        var workStyleString = ""

                        for (index, workStyle) in workStyleArray.enumerated() {
                            if let workStyleType = workStyle.workStyleType,
                               workStyleType.isEmpty == false {
                                if index == 0 {
                                    workStyleString += workStyleType
                                } else {
                                    workStyleString += "," + workStyleType
                                }
                            }
                        }

                        if workStyleString.isEmpty == true {
                            workStyleString = "指定なし"
                        }
                        print(workStyleString)
                    }
                }
                print("end")


                for (index, searchCondition) in searchConditionArray.enumerated() {
                    if index == 0 {
                        continue
                    }

                    self.delete(searchCondition: searchCondition)
                }
            }

        } catch let error as NSError {
            // FIXME: send error report
            print("error.description = \(error.description)")
        }
    }

    func delete(searchCondition: SearchCondition){
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            // FIXME: send error report
            return
        }

        let viewContext = appDelegate.persistentContainer.viewContext

        viewContext.delete(searchCondition)

        do {

            try viewContext.save()
        } catch let error as NSError {
            // FIXME: send error report
            print("error.description = \(error.description)")
            return
        }

    }
}
import UIKit
import CoreData

class WorkStyleManager: NSObject {
    static let sharedManager = WorkStyleManager()

    private override init() {

    }

    func create() -> WorkStyle? {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            // FIXME: send error report
            return nil
        }

        let viewContext = appDelegate.persistentContainer.viewContext

        guard let workStyleEntity = NSEntityDescription.entity(forEntityName: WorkStyle.className, in: viewContext) else {
            // FIXME: send error report
            return nil
        }

        guard let workStyle = NSManagedObject(entity: workStyleEntity, insertInto: viewContext) as? WorkStyle else {
            return nil
        }

        do {
            try viewContext.save()
            return workStyle
        } catch let error as NSError {
            // FIXME: send error report
            print("error.description = \(error.description)")
            return nil
        }
    }
}

自分のCasdate Deleteの実装は、更新前の親レコード取得して、書き換えて、更新ではなく、新規作成して、保存します。
そして、保存が成功したら、古い親レコードを削除するという実装です。

他にこういうやり方があるよ、実装間違ってるよっていうところがあれば、ぜひ教えてください。

以上、遅くなりましたが、2016年12日10日アドベントカレンダーでした。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0