【问题标题】:How to break Core Data mult-threading retain cycles on merging changes?如何在合并更改时打破 Core Data 多线程保留周期?
【发布时间】:2014-01-22 16:36:54
【问题描述】:

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

我按照 raywenderlich 的教程完成了它,它有很多代码 - 但它通常可以正常工作 - 会在需要时发布其中的一部分。 我陷入了一个我无法理解的问题。

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

我正在使用 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];

但我不知道为什么我必须这样做,以及这是否会破坏其他东西。 也许有更好的方法在后台刷新数据,但我完全走错了路。

【问题讨论】:

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


    【解决方案1】:

    一般来说,除了普通的 Foundation 内存管理规则(引用计数)之外,您不应该对托管对象做任何特殊的事情。因此,请确保在不需要它们时不要将它们保留在任何地方。

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

    我注意到您的代码中还有其他一些内容。

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

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

    【讨论】:

    • 您的“其他事情”得到了很好的观察。但是,何时我们需要部分修剪对象图?我认为这种“修剪”是指在没有外部引用的情况下释放“生命”对象,从而释放不必要的占用 RAM。 RAM 始终是移动设备上的一个问题,因此我们需要总是发送refreshObject:mergeChanges: 以强制故障:/
    • 这里有一些很好的信息,但请注意您的前两段是错误的。 Core Data 确实有保留周期,您需要注意并打破这些周期。 developer.apple.com/mac/library/documentation/cocoa/Conceptual/…
    • 只有在仍然保留这些对象时才需要破坏它。如果你不这样做,Core Data 将在关键时释放数据,例如内存不足警告。如果正常的内存管理规则在这里不适用,内存将随着时间的推移无限增长。另请参阅 Marcus Zarra 的评论:stackoverflow.com/a/3332177/1812858
    【解决方案2】:

    这是由保留周期引起的。在处理托管对象时非常常见。见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

    【讨论】:

    • 那么我应该在什么时候做呢?到目前为止,它们之间的保留周期并不重要,因为它适用于单线程并且当我不在线程之间进行合并时。
    • 每次我从 SQLLite 加载数据并完成它时,我是否必须这样做?所以基本上在我的情况下每次我配置一个单元格?以及如何将其与合并结合起来?
    猜你喜欢
    • 2012-09-19
    • 1970-01-01
    • 2015-09-25
    • 1970-01-01
    • 1970-01-01
    • 2013-04-06
    • 1970-01-01
    • 2011-06-16
    • 1970-01-01
    相关资源
    最近更新 更多