はじめに
タイトルを悩んだが適切なタイトルの表現が難しい。Core DataにはcreateOrUpdateは存在しないが、似たようなことをしようとした場合について。
まずConstraintsをidとして設定し、そのConstraintsに従ってcontext.saveを実行する場合を想定。その際にマージポリシーによって更新されたりされなかったりが決まる。
createOrUpdate実例
これはあくまでマージポリシーによってCreateOrUpdateっぽいことをするという例です。安全にUpdateするためにはデータをidで探してきてそれがなければCreateし、あればUpdateするのが良いはずです。
nameプロパティにセットしない場合
マージポリシーがNSMergeByPropertyObjectTrumpMergePolicy
マージポリシーを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はどうなるのか