CoreDataのNSManagedObjectContext
インスタンスのsave:
を実行した時にNSErrorが以下のエラーを返す。
NSError: code=133020, domain=NSCocoaErrorDomain, userInfo= { conflictList= ( NSMergeConflict (0x28164b080) for NSManagedObject (0x282e0ba70) with objectID '0xd068d480c425f056 <x-coredata://26EDCB60-2D7B-472B-863A-C88D60F76034/****>' with oldVersion = 5 and newVersion = 7 and old object snapshot = { ... } and new cached row = { ... };
エラーの原因
CodaDataでは以下のような状況でNSMergeConfilict
が発生する。
- スレッド1の
NSManagedObjectContext
でNSManagedObject
インスタンスのデータを取得する - スレッド2の
NSManagedObjectContext
でスレッド1と同じNSManagedObject
インスタンスのデータを取得する - スレッド1で
NSManagedObject
インスタンスのプロパティを更新し、save
を呼び出す - スレッド2で
NSManagedObject
インスタンスのプロパティを更新し、save
を呼び出す。この時、スレッド1が更新したプロパティと同じプロパティを更新していると、データの競合が発生するためNSErrorがNSMergeConfilict
を返す。
*スレッド1とスレッド2のNSManagedObjectContext
が同じインスタンスの場合は上記のエラーは発生しない。ただし、NSManagedObjectContext
はスレッドセーフではないので、そのような使い方をするとメモリアクセス違反が発生してアプリがクラッシュする場合がある。
CoreDataでは上記の状況が発生した場合、NSManagedObjectContext
のmergePolicy
に従って競合したデータのマージ処理が行われる。初期状態ではmergePolicy
はNSErrorMergePolicy
になっているため、NSErrorでエラーが返る。
簡単な解決方法
NSManagedObjectContext
のmergePolicy
をNSErrorMergePolicy
以外に変更するとエラーは発生せず、以下のルールに従ってデータの更新が行われる。
mergePolicyの値 | データの競合が発生した場合に行われる処理 |
---|---|
NSErrorMergePolicy | NSErrorがエラーを返す |
NSMergeByPropertyObjectTrumpMergePolicy | 競合したプロパティには更新しようとしたデータの値(上記の例の場合はスレッド2のデータの値)が使用される。それ以外のプロパティは両方の変更が適用される。 |
NSMergeByPropertyStoreTrumpMergePolicy | 競合したプロパティには保存済みのデータの値(上記の例の場合はスレッド1のデータの値)が使用される。それ以外のプロパティは両方の変更が適用される。 |
NSOverwriteMergePolicy | 更新しようとしたデータで上書きする。上記の例の場合はスレッド2のデータで更新され、スレッド1が更新したデータは失われる。 |
NSRollbackMergePolicy | 更新しようとしたデータは破棄される。上記の例の場合はスレッド1のデータが残り、スレッド2が更新しようとしたデータは失われる。 |
理想的な解決方法
mergePolicy
の変更によってエラーが発生することはなくなるが、データの更新が一部失われることには変わりがない。理想的な解決方法としては以下の記事が紹介するようにデータの競合が発生しないように処理を書き換えることが望ましい。
以下の記事ではデータの更新を一つのNSOperationQueue
内でのみ実行することによってデータの競合を防ぐ方法が紹介されている。