mik*_*dev 6 core-data grand-central-dispatch ios
简而言之,我正在尝试使用后台队列将从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];
});
Run Code Online (Sandbox Code Playgroud)
"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;
});
}
}
Run Code Online (Sandbox Code Playgroud)
正如您在上面的方法中所看到的,我在主线程上调用"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];
});
Run Code Online (Sandbox Code Playgroud)
这是由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;
});
}
Run Code Online (Sandbox Code Playgroud)
更新:
所以我找到了解决方案.事实证明,我在后台线程上保存对象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];
});
Run Code Online (Sandbox Code Playgroud)
这是处理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;
});
}
Run Code Online (Sandbox Code Playgroud)
Fru*_*eek 25
我要把它扔出去.停止遵循"核心数据编程指南"中列出的最佳并发实践.由于添加了更容易使用的嵌套上下文,因此Apple尚未对其进行更新.此视频详细介绍:https: //developer.apple.com/videos/wwdc/2012/?id = 214
设置主要上下文以使用主线程(适用于处理UI):
NSManagedObjectContext * context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[context setPersistentStoreCoordinator:yourPSC];
Run Code Online (Sandbox Code Playgroud)
对于您可能正在执行并发操作的任何对象,请创建要使用的专用队列上下文
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];
Run Code Online (Sandbox Code Playgroud)
QueueConcurrencyType指的是上下文将对其进行获取(保存和获取请求)操作的队列.NSMainQueueConcurrencyType上下文在主队列上完成所有工作,这使其适用于UI交互.NSPrivateQueueConcurrencyType在它自己的专用队列上执行.因此,当您在backgroundContext上调用save时,它会自动合并调用parentContext的私有数据performBlock.您不希望调用performBlock专用队列上下文,以防它恰好位于将导致死锁的主线程上.
如果你想变得非常花哨,你可以创建一个主要上下文作为私有队列并发类型(适用于后台保存),只有你的UI的主队列上下文,然后是主队列上下文的子上下文用于后台操作(喜欢进口).
我看到你找到了一个适合你的答案.但是我一直有一些类似的问题,想分享我的经验,看看它对你或其他看这种情况的人是否有帮助.
多线程核心数据的东西总是有点令人困惑,所以如果我误读你的代码,请原谅我.但似乎可能有一个更简单的答案.
您在第一次尝试中遇到的核心问题是您将托管对象ID(假设可以在线程之间传递的对象标识符)保存到全局变量以供在主线程上使用.你在后台线程上做到了这一点.问题是您在保存到后台线程的托管对象上下文之前执行了此操作.在保存之前,对象ID传递给另一个线程/上下文对是不安全的.你保存时他们可以改变.请参阅objectID:NSManagedObject参考文档中的警告
您通过通知后台线程保存来修复此问题,并在该线程内部,从通知对象中获取现在安全使用 - 因为上下文已保存的对象ID.这些被传递给主线程,并且实际更改也通过调用mergeChangesFromContextDidSaveNotification合并到主线程中.这是您可以保存一两步的地方.
您正在注册以在后台线程上听到NSManagedObjectContextDidSaveNotification .您可以注册以在主线程上听到相同的通知.在该通知中,您将拥有可在主线程上安全使用的相同对象ID.可以使用mergeChangesFromContextDidSaveNotification和传递的通知对象安全地更新主线程MOC,因为该方法旨在以这种方式工作:mergeChanges docs.只要将moc与调用完成块的线程匹配,就可以从任一线程调用完成块.
因此,您可以在主线程上执行所有主线程更新,完全分离线程,避免必须打包和重新打包更新的内容,或者对持久存储执行相同更改的双重保存.
要清楚 - 发生的合并是在托管对象上下文中它的内存状态 - 主线程上的moc被更新以匹配后台线程上的moc,但是由于你已经保存了这些,因此不需要新的保存.在后台线程上更改到商店.您可以通过线程安全访问通知对象中的任何更新对象,就像在后台线程中使用它时一样.
我希望你的解决方案对你有用,你不必重新考虑因素 - 但是想把这些想法添加到可能会看到这个的人身上.如果我误解了你的代码,请告诉我,我会修改.
在你的情况下,因为你写到后台 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];
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
13131 次 |
| 最近记录: |