核心数据保存对象在后台问题 [英] Core Data saving objects in background issue

查看:80
本文介绍了核心数据保存对象在后台问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简单来说,我要使用后台队列来保存从Web服务提取到Core Data Sqlite3数据库的JSON对象。保存在我通过GCD创建的序列化后台队列上,并保存到为该后台队列创建的NSManagedObjectContext的辅助实例。一旦保存完成,我需要用新创建/更新的对象更新主线程上的NSManagedObjectContext的实例。我所遇到的问题是,在主线程上的NSManagedObjectContext的实例不能找到保存在后台上下文的对象。下面是我采取的代码示例的操作的列表。对于我做错了什么想法?




  • 通过GCD创建后台队列,运行所有预处理逻辑,然后保存背景上下文:



  //在后台队列中处理
dispatch_async(backgroundQueue,^(void){

if(savedObjectIDs.count> 0){
[savedObjectIDs removeAllObjects];
}
if(savedObjectClass){
savedObjectClass = nil;
}

//设置名称
NSThread * currentThread = NSThread currentThread];
[currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME];

//如果没有背景上下文,则创建一个
if(!_backgroundQueueManagedObjectContext){
NSPersistentStoreCoordinator * coordinator = [self persistentStoreCoordinator];
if(coordinator!= nil){
_backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init];
[_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator];
}
}

//保存从关键路径最上层开始的JSON字典,并返回数组中所有已创建/更新的对象
NSArray * objectIds = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0];

//将对象ID和完成块保存到全局变量,以便我们可以在保存
后访问它们if(objectIds){
[savedObjectIDs addObjectsFromArray:objectIds];
}
if(completion){
saveCompletionBlock = completion;
}
if(managedObjectClass){
savedObjectClass = managedObjectClass;
}

//保存所有更改对象上下文
[self saveManagedObjectContext];
});




  • saveManagedObjectContext方法基本上查看哪个线程运行并保存适当的上下文。


  • 所有这些代码都位于单例程序中,并且在单例程序的init方法我为NSManagedObjectContextDidSaveNotification添加一个监听器,并调用mergeChangesFromContextDidSaveNotification:方法




  //从上下文合并更改保存通知到主上下文
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification
{
NSThread * currentThread = [NSThread currentThread];

if([currentThread.name isEqual:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]){

//合并更改到主上下文,并等待操作在主线程上完成
[_managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification :) withObject:notification waitUntilDone:YES];

//在主线程上获取所有新数据并调用完成块
dispatch_async(dispatch_get_main_queue(),^ {

//从数据库获取对象
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];
}
}

//从数组中删除所有保存的对象ID
[savedObjectIDs removeAllObjects];
savedObjectClass = nil;

//调用完成块
//完成(对象);
saveCompletionBlock(objects);

//清除保存的完成块
saveCompletionBlock = nil;
});
}
}

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



此外,对于错误代码格式化,我表示歉意,



更新:



b

我进行了下面建议的更改,但仍然有同样的问题。



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

  //在后台队列中处理
dispatch_async(backgroundQueue,^(void){

if(savedObjectIDs.count> 0){
[savedObjectIDs removeAllObjects];
}
if(savedObjectClass){
savedObjectClass = nil;
}

//设置thead名称
NSThread * currentThread = [NSThread currentThread];
[currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME];

//如果没有后台上下文,则创建一个
if(!_backgroundQueueManagedObjectContext ){
NSPersistentStoreCoordinator * coordinator = [self persistentStoreCoordinator];
if(coordinator!= nil){
_backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init];
[_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator ];
}
}

//保存从关键路径最上层开始的JSON字典
NSArray * objectIds = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0];

//将对象ID和完成块保存到全局变量,以便我们可以在保存
后访问它们if(objectIds){
[savedObjectIDs addObjectsFromArray:objectIds];
}
if(completion){
saveCompletionBlock = completion;
}
if(managedObjectClass){
savedObjectClass = managedObjectClass;
}

//从上下文监听合并更改保存通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground :) name:NSManagedObjectContextDidSaveNotification object: _backgroundQueueManagedObjectContext];

//保存所有更改对象上下文
[self saveManagedObjectContext];
});

这是由NSManagedObjectContextDidSaveNotification通知调用的代码

  //从上下文合并更改保存通知到主上下文
- (void)mergeChangesFromBackground:(NSNotification *)notification
{
// kill the listener
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext];

NSThread * currentThread = [NSThread currentThread];

//合并更改为主上下文,并等待动作在主线程上完成
[[self managedObjectContext] performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification :) withObject:notification waitUntilDone:是];

//调度完成块
dispatch_async(dispatch_get_main_queue(),^ {

//从数据库获取对象
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];
}
}

//从数组中删除所有保存的对象ID
[savedObjectIDs removeAllObjects];
savedObjectClass = nil;

//调用完成块
//完成(对象);
saveCompletionBlock(objects);

//清除保存的完成块
saveCompletionBlock = nil;
});
}

UPDATE:



所以我找到了解决方案。事实证明,我在后台线程上保存对象ID,然后尝试在主线程上使用它们来重新获取它们的方式没有解决。所以我最终从与NSManagedObjectContextDidSaveNotification通知一起发送的userInfo字典中提取插入/更新的对象。



与之前一样,此代码开始预处理和保存逻辑。

  //在后台队列中处理
dispatch_async(backgroundQueue,^(void){

//设置thead名称
NSThread * currentThread = [NSThread currentThread];
[currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME];

[self logMessage:[NSString stringWithFormat:@(%@)saveJSONObjects:objectMapping:class:completion: ,[managedObjectClass description]]];

//如果没有背景上下文,则创建一个
if(!_backgroundQueueManagedObjectContext){
NSPersistentStoreCoordinator * coordinator = [self persistentStoreCoordinator];
if(coordinator!= nil){
_backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init];
[_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator];
}
}

//保存从关键路径最上层开始的JSON字典
[self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext :_backgroundQueueManagedObjectContext level:0];

//将对象ID和完成块保存到全局变量,这样我们可以在保存后访问它们
if(completion){
saveCompletionBlock = completion;
}

//从上下文监听合并更改保存通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground :) name:NSManagedObjectContextDidSaveNotification object: _backgroundQueueManagedObjectContext];

//保存所有更改对象上下文
[self saveManagedObjectContext];
});

这是处理NSManagedObjectContextDidSaveNotification的修改方法

   - (void)mergeChangesFromBackground:(NSNotification *)notification 
{
// kill the listener
[[NSNotificationCenter defaultCenter] removeObserver:self name :NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext];

//合并更改为主上下文,并等待动作在主线程上完成
[[self managedObjectContext] performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification :) withObject:notification waitUntilDone:是];

//调度完成块
dispatch_async(dispatch_get_main_queue(),^ {

//拉取通知中保存的对象,在主线程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& {
[modifiedObjects addObjectsFromArray:[insertedObject allObjects]];
}
if(updatedObject&& updatedObject.count> 0){
[modifiedObjects addObjectsFromArray:[updatedObject allObjects ]];
}

NSMutableArray * objects = [[NSMutableArray alloc] init];

//遍历更新的对象并在主线程中找到它们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;

//调用完成块
saveCompletionBlock(objects);

//清除保存的完成块
saveCompletionBlock = nil;
});
}


解决方案

到后台moc,mergeChangesFromContextDidSaveNotification的通知将出现在后台moc上,而不是前台moc。



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



当你收到该调用时,你可以发送消息到主线程moc到mergeChangesFromContextDidSaveNotification。



andrew



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

  //在后台线程注册
NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(mergeChanges :) name:NSManagedObjectContextDidSaveNotification object:backgroundMOC];

- (void)mergeChanges:(NSNotification *)notification {
NSManagedObjectContext * mainThreadMOC = [singleton managedObjectContext];

//这告诉主线程moc在主线程上运行,并合并到更改
[mainThreadMOC performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification :) withObject:notification waitUntilDone:YES] ;
}


What I'm trying todo in a nutshell is I am using a background queue to save JSON objects pulled from a web service to the Core Data Sqlite3 database. The saving takes place on a serialized background queue I've created via GCD, and saved to a secondary instance of NSManagedObjectContext that is created for that background queue. Once the save is complete I need to update the instance of NSManagedObjectContext that is on the main thread with the newly created/updated objects. The problem I am having though is the instance of NSManagedObjectContext on the main thread is not able to find the objects that were saved on the background context. Below is a list of actions I'm taking with code samples. Any thoughts on what I'm doing wrong?

  • Create a background queue via GCD, run all pre-processing logic and then save the background context on that thread:

.

// 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];
});

  • The "saveManagedObjectContext" method basically looks at which thread is running and saves the appropriate context. I have verified that this method is working correctly so I will not place the code here.

  • All of this code resides in a singleton, and in the singleton's init method I am adding a listener for the "NSManagedObjectContextDidSaveNotification" and it calls the mergeChangesFromContextDidSaveNotification: method

.

// 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;
        });
    }
}

As you can see in the method above I am calling the "mergeChangesFromContextDidSaveNotification:" on the main thread, and I have set the action to wait until done. According to the apple documentation the background thread should wait until that action is complete before it continues with the rest of the code below that call. As I mentioned above once I run this code everything seems to work, but when I try to print out the fetched objects to the console I don't get anything back. It seems that the merge is not in fact taking place, or possibly not finishing before the rest of my code runs. Is there another notification that I should be listening for to ensure that the merge has completed? Or do I need to save the main object context after the merge, but before the fecth?

Also, I apologize for the bad code formatting, but it seems that SO's code tags don't like method definitions.

Thanks guys!

UPDATE:

I've made the changes that were recommended below, but still having the same problem. Below is the updated code I have.

This is the code that invokes the background thread saving processes

// 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];
});

This is the code that is called with by the NSManagedObjectContextDidSaveNotification notification

    // 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;
    });
}

UPDATE:

So I found the solution. Turns out that the way I was saving out the object IDs on the background thread and then trying to use them on the main thread to re-fetch them wasn't working out. So I ended up pulling the inserted/updated objects from the userInfo dictionary that is sent with the NSManagedObjectContextDidSaveNotification notification. Below is my updated code that is now working.

As before this code starts the pre-prossesing and saving logic

// 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];
});

This is the modified method that handles the 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;
    });
}

解决方案

in your case because your writing to the background moc the notification for mergeChangesFromContextDidSaveNotification will come in on the background moc, not the foreground moc.

so you'll need to register for notifications on the background thread coming to the background moc object.

when you receive that call you can send a message to the main thread moc to mergeChangesFromContextDidSaveNotification.

andrew

update: here's a sample that should work

    //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];
}

这篇关于核心数据保存对象在后台问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆