LoginSignup
1
1

More than 1 year has passed since last update.

Core DataのcreateOrUpdateはマージポリシーによって変化する

Last updated at Posted at 2021-08-23

はじめに

タイトルを悩んだが適切なタイトルの表現が難しい。Core DataにはcreateOrUpdateは存在しないが、似たようなことをしようとした場合について。

まずConstraintsをidとして設定し、そのConstraintsに従ってcontext.saveを実行する場合を想定。その際にマージポリシーによって更新されたりされなかったりが決まる。

createOrUpdate実例

これはあくまでマージポリシーによってCreateOrUpdateっぽいことをするという例です。安全にUpdateするためにはデータをidで探してきてそれがなければCreateし、あればUpdateするのが良いはずです。

nameプロパティにセットしない場合

マージポリシーがNSMergeByPropertyObjectTrumpMergePolicy

スクリーンショット 2021-08-23 17.45.53のコピー.png

マージポリシーをNSMergeByPropertyObjectTrumpMergePolicyに切り替え、DBをクリアした結果は次の通り。

@objc(Person)
public class Person: NSManagedObject {
    @NSManaged public var id: Int32
    @NSManaged public var name: String?
}
func createPerson() {

    func debug(_ person: Person) {
        print("---")
        print("person.id:", person.id)
        print("person.name:", person.name ?? "nil")
    }

    let context = persistentContainer.viewContext
    context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy // このポリシーによって変わる

    do {
        let person = Person(context: context)
        person.id = 1
        person.name = "p1"

        saveContext()

        let request = NSFetchRequest<Person>(entityName: "Person")

        let entities = try! context.fetch(request)
        debug(entities.first!)
    }

    do {
        let person = Person(context: context)
        person.id = 1
//            person.name = "p1" // ここをセットしない

        saveContext()

        let request = NSFetchRequest<Person>(entityName: "Person")

        let entities = try! context.fetch(request)
        debug(entities.first!)
    }
}

// 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)")
        }
    }
}

出力は下記の通り。nameをセットしなければnilによって上書きされる(というか新しいオブジェクトがid: 1として上書きされる)。

---
person.id: 1
person.name: p1
---
person.id: 1
person.name: nil

マージポリシーがNSMergeByPropertyStoreTrumpMergePolicy

マージポリシーをNSMergeByPropertyStoreTrumpMergePolicyに切り替え、DBをクリアした結果は次の通り。

---
person.id: 1
person.name: p1
---
person.id: 1
person.name: p1

おそらく、すでにこのid: 1は作成されていてマージエラーが起きている。その際、Storeを優先するためにp1を上書きしてない。

Storeを優先するためにnameが書き換えられていればすでにp1でない場合もある。

マージポリシーがNSErrorMergePolicyの場合

マージポリシーをNSErrorMergePolicyに切り替え、DBをクリアした結果は次の通り。

---
person.id: 1
person.name: p1
Fatal error: Unresolved error Error Domain=NSCocoaErrorDomain Code=133021 "(null)" UserInfo={NSExceptionOmitCallstacks=true, conflictList=(
    "NSConstraintConflict (0x600000af8b80) for constraint (\n    id\n): database: 0xf09c831374aa7f8e <x-coredata://135BBA78-677D-4C61-B065-CD5DF8EB0DB5/Person/p1>, conflictedObjects: (\n    \"0xf09c831374a67f8e <x-coredata://135BBA78-677D-4C61-B065-CD5DF8EB0DB5/Person/p2>\"\n)"
)}, ["NSExceptionOmitCallstacks": 1, "conflictList": <__NSArrayM 0x6000011ef3c0>(
NSConstraintConflict (0x600000af8b80) for constraint (
    id
): database: <Person: 0x600003c84d20> (entity: Person; id: 0xf09c831374aa7f8e <x-coredata://135BBA78-677D-4C61-B065-CD5DF8EB0DB5/Person/p1>; data: {
    id = 1;
    name = p1;
}), conflictedObjects: (
    "<Person: 0x600003c9c730> (entity: Person; id: 0xf09c831374a67f8e <x-coredata://135BBA78-677D-4C61-B065-CD5DF8EB0DB5/Person/p2>; data: {\n    id = 1;\n    name = nil;\n})"
)
)
]

初回は作成されたが次に同じidで更新しようとするとコンフリクトが起こり、マージを失敗させる。

おわりに

マージポリシーのデフォルトはNSErrorMergePolicyのため、比較的ミスに気づきやすいかもしれない。

関連

Realmはどうなるのか

1
1
0

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
1
1