核心数据保存对象在后台问题

我一直在尝试的是我正在使用一个后台队列来保存从Web服务拉到Core Data Sqlite3数据库的JSON对象。 保存发生在我通过GCD创build的序列化背景队列中,并保存到为该背景队列创build的NSManagedObjectContext的辅助实例中。 一旦保存完成,我需要用新创build/更新的对象更新主线程上的NSManagedObjectContext的实例。 我遇到的问题是主线程上的NSManagedObjectContext的实例无法find保存在后台上下文中的对象。 以下是我使用代码示例所采取的操作列表。 任何想法,我在做什么错了?

  • 通过GCD创build一个后台队列,运行所有的预处理逻辑,然后保存该线程的后台上下文:

// process in the background queue dispatch_async(backgroundQueue, ^(void){ if (savedObjectIDs.count > 0) { [savedObjectIDs removeAllObjects]; } if (savedObjectClass) { savedObjectClass = nil; } // set the thead name NSThread *currentThread = [NSThread currentThread]; [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]; // if there is not already a background context, then create one if (!_backgroundQueueManagedObjectContext) { NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init]; [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator]; } } // save the JSON dictionary starting at the upper most level of the key path, and return all created/updated objects in an array NSArray *objectIds = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0]; // save the object IDs and the completion block to global variables so we can access them after the save if (objectIds) { [savedObjectIDs addObjectsFromArray:objectIds]; } if (completion) { saveCompletionBlock = completion; } if (managedObjectClass) { savedObjectClass = managedObjectClass; } // save all changes object context [self saveManagedObjectContext]; }); 
  • “saveManagedObjectContext”方法基本上看哪个线程正在运行并保存适当的上下文。 我已经证实,这个方法工作正常,所以我不会把代码放在这里。

  • 所有这些代码驻留在一个单身人士,在单身人士的init方法,我添加一个“NSManagedObjectContextDidSaveNotification”的监听器,它调用mergeChangesFromContextDidSaveNotification:方法

 // merge changes from the context did save notification to the main context - (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification { NSThread *currentThread = [NSThread currentThread]; if ([currentThread.name isEqual:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]) { // merge changes to the primary context, and wait for the action to complete on the main thread [_managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; // on the main thread fetch all new data and call the completion block dispatch_async(dispatch_get_main_queue(), ^{ // get objects from the database NSMutableArray *objects = [[NSMutableArray alloc] init]; for (id objectID in savedObjectIDs) { NSError *error; id object = [_managedObjectContext existingObjectWithID:objectID error:&error]; if (error) { [self logError:error]; } else if (object) { [objects addObject:object]; } } // remove all saved object IDs from the array [savedObjectIDs removeAllObjects]; savedObjectClass = nil; // call the completion block //completion(objects); saveCompletionBlock(objects); // clear the saved completion block saveCompletionBlock = nil; }); } } 

正如你在上面的方法中所看到的,我在主线程上调用了“mergeChangesFromContextDidSaveNotification:”,并且我已经设置了动作,直到完成。 根据苹果文档,后台线程应该等到该操作完成之后才能继续执行下面的代码。 正如我上面提到的,一旦我运行这个代码一切似乎工作,但是当我尝试打印出提取的对象到控制台我什么都没有得到任何回报。 看起来合并实际上并没有发生,或者在我的其他代码运行之前可能没有完成。 是否还有另外一个通知,我应该倾听,以确保合并完成? 或者我需要保存合并后的主要对象上下文,但在fecth之前?

另外,我为不好的代码格式道歉,但似乎SO的代码标签不喜欢方法定义。

多谢你们!

更新:

我已经做了以下推荐的更改,但仍然有相同的问题。 下面是我有更新的代码。

这是调用后台线程保存进程的代码

 // process in the background queue dispatch_async(backgroundQueue, ^(void){ if (savedObjectIDs.count > 0) { [savedObjectIDs removeAllObjects]; } if (savedObjectClass) { savedObjectClass = nil; } // set the thead name NSThread *currentThread = [NSThread currentThread]; [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]; // if there is not already a background context, then create one if (!_backgroundQueueManagedObjectContext) { NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init]; [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator]; } } // save the JSON dictionary starting at the upper most level of the key path NSArray *objectIds = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0]; // save the object IDs and the completion block to global variables so we can access them after the save if (objectIds) { [savedObjectIDs addObjectsFromArray:objectIds]; } if (completion) { saveCompletionBlock = completion; } if (managedObjectClass) { savedObjectClass = managedObjectClass; } // listen for the merge changes from context did save notification [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground:) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; // save all changes object context [self saveManagedObjectContext]; }); 

这是通过NSManagedObjectContextDidSaveNotification通知调用的代码

  // merge changes from the context did save notification to the main context - (void)mergeChangesFromBackground:(NSNotification *)notification { // kill the listener [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; NSThread *currentThread = [NSThread currentThread]; // merge changes to the primary context, and wait for the action to complete on the main thread [[self managedObjectContext] performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; // dispatch the completion block dispatch_async(dispatch_get_main_queue(), ^{ // get objects from the database NSMutableArray *objects = [[NSMutableArray alloc] init]; for (id objectID in savedObjectIDs) { NSError *error; id object = [[self managedObjectContext] existingObjectWithID:objectID error:&error]; if (error) { [self logError:error]; } else if (object) { [objects addObject:object]; } } // remove all saved object IDs from the array [savedObjectIDs removeAllObjects]; savedObjectClass = nil; // call the completion block //completion(objects); saveCompletionBlock(objects); // clear the saved completion block saveCompletionBlock = nil; }); } 

更新:

所以我find了解决scheme。 事实certificate,我在后台线程上保存对象ID的方式,然后尝试在主线程上使用它们来重新获取它们的方式并没有解决。 所以我最终从用NSManagedObjectContextDidSaveNotification通知发送userInfo字典拉插入/更新的对象。 以下是我现在正在更新的代码。

和以前一样,这个代码启动预先存储和保存逻辑

 // process in the background queue dispatch_async(backgroundQueue, ^(void){ // set the thead name NSThread *currentThread = [NSThread currentThread]; [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]; [self logMessage:[NSString stringWithFormat:@"(%@) saveJSONObjects:objectMapping:class:completion:", [managedObjectClass description]]]; // if there is not already a background context, then create one if (!_backgroundQueueManagedObjectContext) { NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init]; [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator]; } } // save the JSON dictionary starting at the upper most level of the key path [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0]; // save the object IDs and the completion block to global variables so we can access them after the save if (completion) { saveCompletionBlock = completion; } // listen for the merge changes from context did save notification [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground:) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; // save all changes object context [self saveManagedObjectContext]; }); 

这是处理NSManagedObjectContextDidSaveNotification的修改方法

 - (void)mergeChangesFromBackground:(NSNotification *)notification { // kill the listener [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; // merge changes to the primary context, and wait for the action to complete on the main thread [[self managedObjectContext] performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; // dispatch the completion block dispatch_async(dispatch_get_main_queue(), ^{ // pull the objects that were saved from the notification so we can get them on the main thread MOC NSDictionary *userInfo = [notification userInfo]; NSMutableArray *modifiedObjects = [[NSMutableArray alloc] init]; NSSet *insertedObject = (NSSet *)[userInfo objectForKey:@"inserted"]; NSSet *updatedObject = (NSSet *)[userInfo objectForKey:@"updated"]; if (insertedObject && insertedObject.count > 0) { [modifiedObjects addObjectsFromArray:[insertedObject allObjects]]; } if (updatedObject && updatedObject.count > 0) { [modifiedObjects addObjectsFromArray:[updatedObject allObjects]]; } NSMutableArray *objects = [[NSMutableArray alloc] init]; // iterate through the updated objects and find them in the main thread MOC for (NSManagedObject *object in modifiedObjects) { NSError *error; NSManagedObject *obj = [[self managedObjectContext] existingObjectWithID:object.objectID error:&error]; if (error) { [self logError:error]; } if (obj) { [objects addObject:obj]; } } modifiedObjects = nil; // call the completion block saveCompletionBlock(objects); // clear the saved completion block saveCompletionBlock = nil; }); } 

在你的情况,因为你的背景moc的通知mergeChangesFromContextDidSaveNotification的通知将进入后台moc,而不是前景moc。

所以你需要注册后台线程通知到后台moc对象。

当你收到这个调用的时候,你可以向主线程moc发送一条消息给mergeChangesFromContextDidSaveNotification。

安德鲁

更新:这是一个应该工作的示例

  //register for this on the background thread NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundMOC]; - (void)mergeChanges:(NSNotification *)notification { NSManagedObjectContext *mainThreadMOC = [singleton managedObjectContext]; //this tells the main thread moc to run on the main thread, and merge in the changes there [mainThreadMOC performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; } 

我要把它扔在那里。 停止遵循核心数据编程指南中列出的并发的最佳实践 。 苹果并没有更新它,因为添加更容易使用的嵌套上下文。 该video详细介绍如下: https : //developer.apple.com/videos/wwdc/2012/?id = 214

设置您的主要上下文以使用您的主线程(适合处理UI):

 NSManagedObjectContext * context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; [context setPersistentStoreCoordinator:yourPSC]; 

对于您创build的可能正在执行并发操作的任何对象,请创build一个专用队列上下文以供使用

 NSManagedObjectContext * backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [backgroundContext setParentContext:context]; //Use backgroundContext to insert/update... //Then just save the context, it will automatically sync to your primary context [backgroundContext save:nil]; 

QueueConcurrencyType是指上下文将对其进行读取(保存和读取请求)操作的队列。 NSMainQueueConcurrencyType上下文完成了主队列中的所有工作,这使得它适用于UI交互。 一个NSPrivateQueueConcurrencyType在它自己的专用队列上进行。 所以当你在backgroundContext上调用save的时候,它会自动合并使用performBlock来调用parentContext的私有数据。 你不想在私有队列上下文中调用performBlock ,以防碰巧在主线程上导致死锁。

如果你想变得很花哨,你可以创build一个主要的上下文作为一个私有队列并发types(这是适合于后台保存)与一个主队列上下文只是你的用户界面,然后子上下文的主要队列上下文背景操作像import)。

我看到你已经制定了一个适合你的答案。 但是我也遇到了一些类似的问题,想分享一下我的经验,看看对你或者其他人来说,这对你是否有帮助。

multithreading核心数据的东西总是有点混乱阅读,所以请原谅我,如果我误读你的代码。 但似乎可以有一个更简单的答案给你。

您在第一次尝试中遇到的核心问题是您将pipe理对象标识(假定是可以在线程之间传递的对象标识符)保存到全局variables以供在主线程上使用。 你在后台线程上做了这个。 问题是,您在保存到后台线程的托pipe对象上下文之前执行了此操作。 对象ID在保存之前不安全地传递给另一个线程/上下文对。 他们可以改变,当你保存。 请参阅objectID: NSManagedObject参考文档中的警告

通过通知保存的后台线程,并在该线程内部,从通知对象中获取由于上下文已保存的对象ID而可以解决这个问题。 这些被传递给主线程,实际更改也被合并到主线程中,调用mergeChangesFromContextDidSaveNotification。 在这里你可以节省一两步。

您正在注册在后台线程上听到NSManagedObjectContextDidSaveNotification。 您可以注册,而不是在线程上听到相同的通知。 在这个通知中,您将拥有可以在主线程上安全使用的相同对象ID。 使用mergeChangesFromContextDidSaveNotification和传递的通知对象可以安全地更新主线程MOC,因为该方法devise为以这种方式工作: mergeChanges文档 。 只要您将moc与调用完成块的线程匹配,就可以从任一线程调用您的完成块。

因此,您可以在主线程上更新所有主线程,干净地分离线程并避免必须打包和重新包装更新的内容,或者对持久存储进行相同更改的双重保存。

要清楚 – 发生的合并是在托pipe对象的contex和其内存中的状态 – 主线程上的moc更新以匹配后台线程上的moc,但没有必要重新保存,因为您已经保存这些后台线程上的商店更改。 您可以通过线程安全地访问通知对象中的任何更新对象,就像您在后台线程上使用它时一样。

我希望你的解决scheme为你工作,你不需要重新考虑因素 – 但是想把我的想法添加到其他人可能会看到这一点。 请让我知道,如果我误解了你的代码,我会修改。