删除核心数据对象并保存在后台线程中

Posted

技术标签:

【中文标题】删除核心数据对象并保存在后台线程中【英文标题】:Delete Core Data object and save in background thread 【发布时间】:2014-01-20 22:47:39 【问题描述】:

即使阅读了很多关于核心数据的内容,尤其是删除对象,我仍然难以理解核心数据在后台线程中的工作原理。

例如,如果我想从这样的上下文中删除一个对象:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath

    NSManagedObjectContext *context = [self managedObjectContext];
    if (editingStyle == UITableViewCellEditingStyleDelete)
    
        // Delete object from database
        [context deleteObject:[self.tests objectAtIndex:indexPath.row]];
        NSError *error = nil;
        if (![context save:&error]) 
            NSLog(@"Can't Delete! %@ %@", error, [error localizedDescription]);
            return;
        
        [self.tests removeObjectAtIndex:indexPath.row];
        [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    

这行得通,但是当数据很大时,[context save:&error] 需要很多时间,那么我该如何在后台进行呢? 似乎我无法使用其他上下文,否则我会收到错误an nsmanagedobjectcontext cannot delete objects in other contexts。 我尝试了数百种不同的东西,但我迷路了...... 谢谢!

【问题讨论】:

【参考方案1】:

您只能删除object 中的context;您在其中获取了该托管对象。如果您在新上下文中使用单独的thread,在这种情况下,您应该从第一个上下文中获得的managedObject 获取objectId,以便您可以删除对象

[context objectWithID:objectId]];

上下文之间的 NSManagedObjectID 相同,但 NSManagedObject 本身不一样。

【讨论】:

【参考方案2】:

正如提到的另一个答案,您只能从其上下文中删除一个对象,并且上下文是线程绑定的。这里的问题不是保存需要多长时间,而是保存的位置。

您应该避免在像这样的任何面向 UI 的方法调用中进行昂贵的操作。删除后没有理由立即保存。稍后保存,当用户期望 UI 出现延迟时保存。 Core Data 无需保存即可正常工作。

【讨论】:

【参考方案3】:

看看 NSManagedObjectContext 上的 performBlock 和 performBlockAndWait 方法。

我一直倾向于使用 NSPrivateQueueConcurrencyType,即使是我的 UI 绑定托管对象上下文,因为它完全避免了这种情况。例如,您可以这样做:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath

    NSManagedObjectContext *context = [self managedObjectContext];
    if (editingStyle == UITableViewCellEditingStyleDelete)
    
        NSManagedObject *m = [self.tests objectAtIndex:indexPath.row];
        [context performBlock:^
            [context deleteObject:m];

            if (![context save:&error]) 
                //Note: You should really do something more useful than log this
                NSLog(@"Can't Delete! %@ %@", error, [error localizedDescription]);
            
        ];
        [self.tests removeObjectAtIndex:indexPath.row];
        [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    

performBlock 调用是异步的(与其同步对应的 performBlockAndWait 不同),这将允许 tableView 更新继续进行,因为上下文正忙于保存在单独的线程上。

顺便说一句,您的托管对象上下文实例不应该经常更改。小型应用程序在其生命周期内只分配一个 NSManagedObjectContxt 实例是很常见的。

【讨论】:

谢谢!这似乎有效。我只是想知道:只使用 NSPrivateQueueConcurrencyType 可以吗? 只要您使用 performBlock / perfomBlockAndWait 从托管对象中读取就可以了。 Tableview 代码特别有点尴尬,因为您必须从 performBlockAndWait 调用中复制值才能在单元格上设置它们。从 NSMainQueueConcurrencyType 开始(这样您就可以在块中设置单元格属性),然后在有意义的情况下转换到私有队列。

以上是关于删除核心数据对象并保存在后台线程中的主要内容,如果未能解决你的问题,请参考以下文章

在后台线程问题中将服务器数据保存到核心数据

nsfetchresultcontroller 在删除后返回行

iOS/Objective-C:在后台线程中保存到核心数据

Core-Data 后台保存性能问题

在后台线程上安全保存 Core Data 托管对象上下文的正确方法?

核心数据和后台线程的问题