我正在尝试简单地说,我正在使用后台队列将从Web服务中提取的JSON对象保存到Core Data Sqlite3数据库。保存发生在我通过GCD创建的序列化后台队列中,并保存到为该后台队列创建的NSManagedObjectContext的辅助实例中。保存完成后,我需要使用新创建/更新的对象更新主线程上的NSManagedObjectContext实例。我遇到的问题是主线程上的NSManagedObjectContext实例无法找到保存在后台上下文中的对象。以下是我正在使用代码示例执行的操作列表。对我做错了什么的想法?
// 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:”,并且我已将操作设置为等到完成。根据apple文档,后台线程应该等到该操作完成后再继续该调用下面的其余代码。正如我上面提到的,一旦我运行此代码,一切似乎都有效,但是当我尝试将获取的对象打印到控制台时,我没有得到任何回报。似乎合并实际上没有发生,或者可能在我的其余代码运行之前没有完成。是否有其他通知我应该监听以确保合并已完成?或者我需要在合并之后但在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;
});
}
更新:
所以我找到了解决方案。事实证明,我在后台线程上保存对象ID然后尝试在主线程上使用它们来重新获取它们的方式并没有成功。所以我最终从userInfo字典中提取插入/更新的对象,该字典与NSManagedObjectContextDidSaveNotification通知一起发送。以下是我现在正在使用的更新代码。
如前所述,此代码启动预先存储和保存逻辑
// 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;
});
}
答案 0 :(得分:25)
我要把它扔出去。 停止遵循“核心数据编程指南”中列出的最佳并发实践。由于添加了更容易使用的嵌套上下文,因此Apple尚未对其进行更新。此视频详细介绍:https://developer.apple.com/videos/wwdc/2012/?id=214
设置主要上下文以使用主线程(适用于处理UI):
NSManagedObjectContext * context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[context setPersistentStoreCoordinator:yourPSC];
对于您创建的可能正在执行并发操作的任何对象,请创建要使用的专用队列上下文
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
,以防它恰好位于将导致死锁的主线程上。
如果你想变得非常花哨,你可以创建一个主要上下文作为私有队列并发类型(适用于后台保存),只有你的UI的主队列上下文,然后你的主队列上下文的子上下文后台操作(如进口)。
答案 1 :(得分:7)
我看到你找到了一个适合你的答案。但我一直有类似的问题,想分享我的经验,看看它对你或其他人是否有帮助。
多线程核心数据的内容总是让人感到有些困惑,所以如果我误读你的代码,请原谅。但似乎可能有一个更简单的答案。
您在第一次尝试时遇到的核心问题是您将托管对象ID(可能是可在线程之间传递的对象标识符)保存到全局变量以供主线程使用。你在后台线程上做到了这一点。问题是您在保存到后台线程的托管对象上下文之前执行了此操作。在保存之前,对象ID传递给另一个线程/上下文对是不安全的。你保存时他们可以改变。请参阅objectID文档中的警告:NSManagedObject reference
您通过通知后台线程保存来修复此问题,并在该线程内部,从通知对象中获取现在安全使用 - 因为上下文已保存的对象ID。这些被传递给主线程,并且实际更改也通过调用mergeChangesFromContextDidSaveNotification合并到主线程中。这是您可以保存一两步的地方。
您正在注册以在后台主题上听到NSManagedObjectContextDidSaveNotification。您可以注册以在主主题上听到相同的通知。在该通知中,您将拥有可在主线程上安全使用的相同对象ID。可以使用mergeChangesFromContextDidSaveNotification和传递的通知对象安全地更新主线程MOC,因为该方法旨在以这种方式工作:mergeChanges docs。只要将moc与调用完成块的线程匹配,就可以从任一线程调用完成块现在是安全的。
因此,您可以在主线程上执行所有主线程更新,完全分离线程,避免必须打包和重新打包更新的内容,或者对持久存储执行相同更改的双重保存。
要清楚 - 发生的合并是在托管对象上下文及其内存状态 - 主线程上的moc更新以匹配后台线程上的moc,但是因为您不需要新的保存ALREADY将这些更改保存到后台线程上的存储中。您可以通过线程安全访问通知对象中的任何更新对象,就像在后台线程中使用它时一样。
我希望你的解决方案对你有用,而且你不必重新考虑因素 - 但是想为那些可能会看到这个问题的人添加我的想法。如果我误解了您的代码,请告诉我,我会修改。
答案 2 :(得分:4)
在你的情况下,因为你写的后台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];
}