Ios 如何在合并更改时打破核心数据多线程保留周期?

Ios 如何在合并更改时打破核心数据多线程保留周期?,ios,objective-c,multithreading,core-data,retain-cycle,Ios,Objective C,Multithreading,Core Data,Retain Cycle,在我的应用程序中,我使用CoreData存储数据,并使用NSFetchedResultsController显示数据 我遵循raywenderlich的教程来完成它,它有很多代码——但总体来说工作正常——在需要时会发布部分代码。 我在一个我不能理解的问题上卡住了 与NSFetchedResultsController组合在一起的UITableView中显示的数据可以在后台更新—这就是我的问题开始的地方 我正在使用拉入刷新方法,并在单独的线程上开始后台下载。它使用为该线程创建的自己的NSManag

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

我遵循raywenderlich的教程来完成它,它有很多代码——但总体来说工作正常——在需要时会发布部分代码。 我在一个我不能理解的问题上卡住了

与NSFetchedResultsController组合在一起的UITableView中显示的数据可以在后台更新—这就是我的问题开始的地方

我正在使用拉入刷新方法,并在单独的线程上开始后台下载。它使用为该线程创建的自己的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];
但我不知道我为什么要这么做,如果那会破坏其他东西。
也许有更好的方法在后台刷新数据,但我完全走错了方向。

这是由保留周期造成的。在处理托管对象时非常常见。看

当托管对象之间存在关系时,每个对象都会维护对与其相关的一个或多个对象的强引用。在托管内存环境中,这会导致保留周期,从而防止释放不需要的对象。为了确保中断保留周期,当您使用完一个对象后,可以使用托管对象上下文方法将其转化为故障

通常使用refreshObject:mergeChanges:刷新托管对象的属性值。如果mergeChanges标志为YES,则该方法将对象的属性值与持久存储协调器中可用的对象的属性值合并。但是,如果该标志为“否”,则该方法只会将对象变回故障状态,而不进行合并,从而使其释放相关的托管对象。这将打破该托管对象与其保留的其他托管对象之间的保留周期

另请注意,上下文将保留对具有挂起更改的托管对象的强引用—插入、删除或更新,直到向上下文发送保存:、重置、回滚或解除锁定消息,或发送适当数量的撤消以撤消更改


此外,核心数据有一个称为用户事件的概念。默认情况下,用户事件正确地包装在主运行循环中,但是对于不在主线程上的MOC,您负责确保用户事件得到正确处理。请参阅和。

这是由保留循环引起的。在处理托管对象时非常常见。看

当托管对象之间存在关系时,每个对象都会维护对与其相关的一个或多个对象的强引用。在托管内存环境中,这会导致保留周期,从而防止释放不需要的对象。为了确保中断保留周期,当您使用完一个对象后,可以使用托管对象上下文方法将其转化为故障

通常使用refreshObject:mergeChanges:刷新托管对象的属性值。如果mergeChanges标志为YES,则该方法将对象的属性值与持久存储协调器中可用的对象的属性值合并。但是,如果该标志为“否”,则该方法只会将对象变回故障状态,而不进行合并,从而使其释放相关的托管对象。这将打破该托管对象与其保留的其他托管对象之间的保留周期

另请注意,上下文将保留对具有挂起更改的托管对象的强引用—插入、删除或更新,直到向上下文发送保存:、重置、回滚或解除锁定消息,或发送适当数量的撤消以撤消更改


此外,核心数据有一个称为用户事件的概念。默认情况下,用户事件正确地包装在主运行循环中,但是对于不在主线程上的MOC,您负责确保用户事件得到正确处理。一般来说,除了正常的基础内存管理规则引用计数之外,您不应该对托管对象做任何特别的事情。所以当你不需要它们的时候,确保你不会把它们留在任何地方

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

我注意到你的代码中还有一些其他的东西

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


在网络操作完成处理程序中,您将工作分派到全局并发队列,并从那里创建新的上下文。通过使用全局并发队列,您无法控制并发任务的数量。有可能是a线程用完,b创建许多新上下文,这些上下文将竞争一个持久存储协调器和一个持久存储。我建议使用串行队列或使用具有管理私有串行队列的私有队列并发类型的上下文进行调度。一般来说,除了正常的基础内存管理规则引用计数之外,不应该对托管对象做任何特殊的操作。所以当你不需要它们的时候,确保你不会把它们留在任何地方

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

我注意到你的代码中还有一些其他的东西

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


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

那么我应该在什么时候这样做呢?到目前为止,它们之间的保留周期并不重要,因为它可以在单个线程上工作,并且在我没有在线程之间进行合并的情况下工作。每次从SQLLite加载数据并完成后,我都必须这样做吗?那么基本上在我的情况下,每次我配置一个单元格?如何将其与合并结合起来?那么在什么时候我应该这样做呢?到目前为止,它们之间的保留周期并不重要,因为它可以在单个线程上工作,并且在我没有在线程之间进行合并的情况下工作。每次从SQLLite加载数据并完成后,我都必须这样做吗?那么基本上在我的情况下,每次我配置一个单元格?如何将其与合并结合起来?你的其他事情都得到了很好的观察。但是,我们什么时候需要部分修剪对象图?我认为这种修剪是指在没有外部参照的情况下释放生命对象,从而释放不必要的内存。在移动设备上,RAM始终是一个问题,因此我们需要始终发送refreshObject:mergeChanges:以强制故障:/此处有一些好信息,但请注意,前两段是错误的。核心数据绝对有保留周期,您需要了解并打破这些周期。仅当仍保留这些对象时,才需要打断它。如果不这样做,核心数据将在关键时释放数据,例如,内存不足警告。如果正常的内存管理规则在这里不适用,那么内存将随着时间无限增长。另请参见马库斯·扎拉(Marcus Zarra)的评论:你的其他事情都得到了很好的观察。但是,我们什么时候需要部分修剪对象图?我认为这种修剪是指在没有外部参照的情况下释放生命对象,从而释放不必要的内存。在移动设备上,RAM始终是一个问题,因此我们需要始终发送refreshObject:mergeChanges:以强制故障:/此处有一些好信息,但请注意,前两段是错误的。核心数据绝对有保留周期,您需要了解并打破这些周期。仅当仍保留这些对象时,才需要打断它。如果不这样做,核心数据将在关键时释放数据,例如,内存不足警告。如果正常的内存管理规则在这里不适用,那么内存将随着时间无限增长。另请参见Marcus Zarra的评论: