如何打破Core Data多线程保留合并更改的周期?

时间:2014-01-22 16:36:55

标签: ios objective-c multithreading core-data retain-cycle

在我的应用程序中,我使用CoreData存储并使用NSFetchedResultsController显示数据。

我按照raywenderlich的教程来完成它并且它是很多代码 - 但它一般都正常工作 - 会在需要时发布部分代码。 我坚持一个我无法理解的问题。

UITableView内部显示的数据与NSFetchedResultsController相结合,可以在后台更新 - 这就是我的问题开始的地方。

我正在进行Pull-to-refresh方法,并在单独的线程中开始下载。它正在使用它为此线程创建的NSManagedObjectContext,并在创建所有对象后保存它。

这是代码:

- (void)refresh:(id)sender
{
[ServerConnection downloadContactsForToken:token success:^(NSDictionary* responseObject)
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSManagedObjectContext* context = [[[AppManager sharedAppManager] createManagedObjectContext] retain];

            NSArray* responseContacts = responseObject[@"contacts"];
            [responseContacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                //ContactDB is NSManagedObject
                [[[ContactDB alloc] initWithDictionary:obj andContext:context] autorelease];
            }];

            [context save:nil];
            [context release];

            dispatch_async(dispatch_get_main_queue(), ^{
                [self.refreshControl endRefreshing];
            });
        });
    }];
}

根据我在Apple文档中读到的,在主线程NSManagedObjectContext上检测这些更改的正确方法是:

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(managedObjectContextDidSave:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:nil];
}

- (void)managedObjectContextDidSave:(NSNotification *)notification {
    dispatch_async(dispatch_get_main_queue(), ^{
        if(notification.object != [[AppManager sharedAppManager] managedObjectContext]) {
            [[[AppManager sharedAppManager] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
        }
    });
}

基本上,当我收到有关任何managedObjectContext更改的通知时,我将更改合并到主线程上下文。它通常起作用,但在我开始分析之后,我发现所有合并到描述过程中的对象都不会被释放。

当我在主线程上执行所有操作时 - 它可以正常工作 - 当我滚动UITableView时,它们会按预期取消分配。

我找到了一个解决方法,我正在打电话:

[[[AppManager sharedAppManager] managedObjectContext] reset];

合并完成后:

[[[AppManager sharedAppManager] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
[[[AppManager sharedAppManager] managedObjectContext] reset];

但是我不知道为什么要这么做,如果那会破坏别的东西。 也许有一种更好的方法可以在后台刷新数据,而且我的轨道完全错误。

2 个答案:

答案 0 :(得分:4)

通常,除了正常的Foundation内存管理规则(引用计数)之外,您不应对托管对象执行任何特殊操作。因此,只要确保在不需要它们时不要将它们保留在任何地方。

只有当您需要部分修剪对象图并仍然具有对对象的强引用时,才需要使用-refreshObject:mergeChanges:将对象库转换为错误。

我在你的代码中注意到了其他一些事情。

您正在订阅所有上下文保存通知。这很危险,因为您可以接收那些您不拥有的上下文的通知。例如,来自地址簿或您正在使用的某些第三方库。

在网络操作完成处理程序中,您将工作分派到全局并发队列并从那里创建新的上下文。通过使用全局并发队列,您无法控制并发任务的数量。有可能a)用完线程,b)创建许多新的上下文,这些上下文将竞争一个持久存储协调器和一个持久存储。我建议调度到串行队列或使用具有管理专用串行队列的专用队列并发类型的上下文。

答案 1 :(得分:3)

这是由保留周期引起的。处理托管对象时非常常见。请参阅Core Data Programming Guide: Memory Management (Breaking Relationship Retain Cycles)

  

当您在托管对象之间建立关系时,每个对象都会对与其相关的一个或多个对象保持强引用。在托管内存环境中,这会导致保留周期(请参阅Object Ownership and Disposal),以防止不需要的对象的重新分配。为确保保留周期被破坏,当您完成对象时,可以使用托管对象上下文方法refreshObject:mergeChanges:将其转换为错误。

     

通常使用refreshObject:mergeChanges:刷新托管对象的属性值。如果mergeChanges标志为YES,则该方法将对象的属性值与持久性存储协调器中可用对象的属性值合并。但是,如果标志为NO,则该方法只是将对象转换回故障而不合并,这会导致它释放相关的管理对象。这会破坏该托管对象与其保留的其他托管对象之间的保留周期。

另请注意,上下文保留对具有挂起更改(插入,删除或更新)的托管对象的强引用,直到上下文发送save:resetrollbackdealloc消息,或撤消更改的适当数量的撤消。

此外,Core Data有一个名为“用户事件”的概念。默认情况下,“用户事件”正确包装在主运行循环中,但是对于不在主线程上的MOC,您负责确保正确处理用户事件。请参阅Use Thread Confinement to Support ConcurrencyTrack Changes in Other Threads Using Notifications