我在 CoreData 中遇到了一些奇怪的行为,导致我的一个 MOC 最终处于不一致的状态。我已经在一个小示例项目中复制了这个问题
这是我的情况的基本概述:
我有两种实体类型,管道和盒子。每个管道可以包含 0 个或多个盒子,每个盒子都是一个管道的一部分
在我的示例项目中:
- 我正在创建 1 个管道和 3 个示例框,它们都指向该管道。
- 此示例数据是在使用以下命令创建的 MOC 上创建的
NSMainQueueConcurrencyType
- 我创建了一个后台 MOC (`NSPrivateQueueConcurrencyType') 并获取所有框并仅删除其中一个。
- 此删除导致管道更新(关系中应该少一个框)
- 当我保存后台 MOC 时,我尝试将更改合并到主队列 MOC
问题是在合并之后,主队列上下文成功合并了删除但没有将编辑合并到管道中,这应该表明关系中少了一个框。
不知何故,它没有合并整个变化。
这是一些代码:
- (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
如您所见,当我第二次打印出 uiContext 时,它处于不一致的状态。具体来说,上下文中有 2 个框,但管道具有指向 3 个框的关系 - 因此不一致。
我知道后台保存可能在第二次打印之前或之后完成,但在任何一种情况下,上下文的状态都应该是一致的,不是吗?(即关系中的 3 个框和 3 个项目或关系中的 2 个框和 2 个项目)。