MOC の 1 つが一貫性のない状態になる原因となる、CoreData での奇妙な動作が発生しています。小さなサンプルプロジェクトで問題を再現しました
これが私の状況の基本的な要約です。
パイプラインとボックスの 2 つのエンティティ タイプがあります。各パイプラインには 0 個以上のボックスを含めることができ、各ボックスは 1 つのパイプラインの一部です
私のサンプルプロジェクトでは:
- 1 つのパイプラインと 3 つのサンプル ボックスをすべてそのパイプラインに向けて作成しています。
- このサンプル データは、で作成された MOC で作成されます。
NSMainQueueConcurrencyType
- バックグラウンド MOC (「NSPrivateQueueConcurrencyType」) を作成し、すべてのボックスをフェッチして、そのうちの 1 つだけを削除します。
- この削除により、パイプラインが更新されます (関係のボックスが 1 つ少なくなるはずです)。
- バックグラウンド MOC を保存すると、変更をメイン キュー MOC にマージしようとします。
問題は、マージ後、メイン キュー コンテキストが削除を正常にマージしたが、編集をパイプラインにマージしなかったことです。これは、リレーション内のボックスが 1 つ少ないことを示しているはずです。
どういうわけか、変更全体をマージしていません。
コードの一部を次に示します。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// create a core database and a moc on the ui thread
[self initCoreData];
// fill up db with dummy data all on the main thread
[self createDummyData:self.uiContext];
[self printUIContextContents];
// now create a background moc
NSManagedObjectContext* backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// when the backgrouynd context saves, merge changees in to UI context
[backgroundContext performBlockAndWait:^{
backgroundContext.mergePolicy = [[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType];
[backgroundContext setPersistentStoreCoordinator:self.persistentCoordinator];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mocDidSave:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];
}];
// now delete a box on background thread
[backgroundContext performBlock:^{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Box"];
request.predicate = [NSPredicate predicateWithFormat:@"name = %@", @"box1"];
NSError* error = nil;
NSArray* boxes = [backgroundContext executeFetchRequest:request error:nil];
if (error != nil){
NSLog(@"Error in deleting box: %@", error);
}
Box* box = (Box*)boxes[0];
[backgroundContext deleteObject:box];
[backgroundContext save:nil];
}];
[self printUIContextContents];
return YES;
}
- (void) printUIContextContents {
[self.uiContext performBlockAndWait:^{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Pipeline"];
NSArray* pipelines = [self.uiContext executeFetchRequest:request error:nil];
for (Pipeline* pipeline in pipelines) {
NSLog(@"Pipeline Name: %@", pipeline.name);
NSLog(@"\tBoxes that are in the pipeline relationship: ");
for (Box* box in pipeline.boxes) {
NSLog(@"\t\t%@", box.name);
}
}
NSLog(@" ");
request = [NSFetchRequest fetchRequestWithEntityName:@"Box"];
NSArray* boxes = [self.uiContext executeFetchRequest:request error:nil];
NSLog(@"All Boxes Entities Present:");
for (Box* box in boxes) {
NSLog(@"\t%@", box.name);
}
NSLog(@" ");
NSLog(@" ");
}];
}
- (void)mocDidSave:(NSNotification *)notif {
[self.uiContext performBlockAndWait:^(void) {
[self.uiContext mergeChangesFromContextDidSaveNotification:notif];
}];
}
- (void) createDummyData:(NSManagedObjectContext*)context {
NSArray* boxNames = [NSArray arrayWithObjects:@"box1", @"box2", @"box3", nil];
NSString* pipelineName = @"pipeline1";
[self.uiContext performBlockAndWait:^{
Pipeline* pipe = (Pipeline*)[self createEntity:@"Pipeline" inContext:self.uiContext];
pipe.name = pipelineName;
for (NSString* boxName in boxNames) {
Box* box = (Box*)[self createEntity:@"Box" inContext:self.uiContext];
box.name = boxName;
box.pipeline = pipe;
}
NSError* error = nil;
[self.uiContext save:&error];
if (error != nil){
NSLog(@"Error in create dummy data: %@", error);
}
}];
}
- (void) initCoreData {
NSURL* modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
NSURL* storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"StreakDB.sqlite"];
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
NSManagedObjectModel* objectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSError *error = nil;
self.persistentCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:objectModel];
if (![self.persistentCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
SOME ERROR HANDLING HERE
*/
}
self.uiContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[self.uiContext setPersistentStoreCoordinator:self.persistentCoordinator];
self.uiContext.mergePolicy = [[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType];
}
- (NSManagedObject*)createEntity:(NSString *)entityType inContext:(NSManagedObjectContext *)context {
return [NSEntityDescription insertNewObjectForEntityForName:entityType
inManagedObjectContext:context];
}
何が起こっているのでしょうか?ここでも、この問題を説明する (そして結果を出力する) サンプル プロジェクトを示します。
アップデート:
パイプライン名:pipeline1 パイプライン関係にあるボックス:box3 box1 box2
存在するすべてのボックス エンティティ: box3 box2 box1
パイプライン名:pipeline1 パイプライン関係にあるボックス:box3 box1 box2
存在するすべてのボックス エンティティ: box3 box2
ご覧のとおり、2 回目に uiContext を出力すると、一貫性のない状態になります。具体的には、コンテキストには 2 つのボックスがありますが、パイプラインには 3 つのボックスを指す関係があるため、矛盾が生じています。
バックグラウンド保存が 2 回目の印刷の前または後に完了している可能性があることは理解していますが、どちらの場合でも、コンテキストの状態は一貫しているはずです。(つまり、3 つのボックスと 3 つのアイテムの関係、または 2 つのボックスと 2 つのアイテムの関係)。