1

在我的应用程序中,我使用 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];

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

4

2 回答 2

4

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

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

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

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

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

于 2014-01-22T18:19:27.610 回答
3

这是由保留周期引起的。在处理托管对象时非常常见。请参阅核心数据编程指南:内存管理(断开关系保留周期)

当您在托管对象之间建立关系时,每个对象都维护对与其相关的一个或多个对象的强引用。在托管内存环境中,这会导致保留循环(请参阅对象所有权和处置),从而防止释放不需要的对象。为确保保留周期被打破,当您完成一个对象时,您可以使用托管对象上下文方法refreshObject:mergeChanges:将其变为故障。

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

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

此外,Core Data 有一个称为“用户事件”的概念。默认情况下,“用户事件”被正确包装在主运行循环中,但是对于不在主线程上的 MOC,您有责任确保正确处理用户事件。请参阅使用线程限制来支持并发使用通知跟踪其他线程中的更改

于 2014-01-22T16:43:04.657 回答