3

我最近重写了我的核心数据驱动的数据库控制器,以使用 Grand Central Dispatch 在后台管理获取和导入。控制器可以在 2 个 NSManagedContext 上操作:

  1. NSManagedObjectContext *mainMoc主线程的实例变量。此上下文仅用于主线程或dipatch_get_main_queue()全局队列对 UI 的快速访问。

  2. NSManagedObjectContext *bgMoc用于后台任务(为表的 NSFetchedresultsController 导入和获取数据)。此后台任务仅由用户定义的队列触发:(dispatch_queue_t bgQueue 数据库控制器对象中的实例变量)。

当执行更大或更复杂的谓词时,在后台获取表的数据不会阻塞用户 UI。

在我的表视图控制器中获取 NSFetchedResultsController 的示例代码:

-(void)fetchData{

dispatch_async([CDdb db].bgQueue, ^{

        NSError *error = nil;
        [[self.fetchedResultsController fetchRequest] setPredicate:self.predicate];
        if (self.fetchedResultsController && ![self.fetchedResultsController performFetch:&error]) {

            NSSLog(@"Unresolved error in fetchData %@", error);
        }

        if (!initial_fetch_attampted)initial_fetch_attampted = YES;
        fetching = NO;

        dispatch_async(dispatch_get_main_queue(), ^{

            [self.table reloadData];
            [self.table scrollRectToVisible:CGRectMake(0, 0, 100, 20) animated:YES];
        });

    });

} // fetchData 函数结束

bgMocmainMoc与保存合并使用NSManagedObjectContextDidSaveNotification

- (void)bgMocDidSave:(NSNotification *)saveNotification {

    // CDdb - bgMoc didsave - merging changes with main mainMoc
    dispatch_async(dispatch_get_main_queue(), ^{

    [self.mainMoc mergeChangesFromContextDidSaveNotification:saveNotification];
     // Extra notification for some other, potentially interested clients
       [[NSNotificationCenter defaultCenter] postNotificationName:DATABASE_SAVED_WITH_CHANGES object:saveNotification];

    });
}

- (void)mainMocDidSave:(NSNotification *)saveNotification {

    // CDdb - main mainMoc didSave - merging changes with bgMoc
    dispatch_async(self.bgQueue, ^{
     [self.bgMoc mergeChangesFromContextDidSaveNotification:saveNotification];
     });
}

NSfetchedResultsController 委托只实现了一种方法(为简单起见):

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {

    dispatch_async(dispatch_get_main_queue(), ^{

        [self fetchData];

    });

}

这样,我试图遵循 Apple 对 Core Data 的建议:每个线程 1 个 NSManagedObjectContext。我知道这种模式并不完全干净,最后有两个原因:

  1. bgQueue挂起后不一定会触发同一个线程,但由于它是串行的,所以应该没什么关系(从来没有 2 个线程尝试访问bgMoc专用于它的 NSManagedObjectContext)。
  2. 有时表视图数据源方法会向 NSFetchedResultsController 询问来自 bgMoc 的信息(因为 fetch 是在 bgQueue 上完成的),例如节数、在节数中获取的对象等。

具有此缺陷的事件这种方法在 95% 的应用程序运行时间中运行良好,直到...

这是我的问题:

有时,应用程序会随机冻结但不会崩溃。它不会对任何触摸做出响应,使其恢复运行的唯一方法是完全重新启动它(切换回后台无济于事)。没有抛出异常,也没有任何内容打印到控制台(我在 Xcode 中为所有异常设置了断点)。

我尝试使用 Instruments(尤其是时间配置文件)对其进行调试,以查看主线程上是否发生了一些困难,但没有出现任何问题。

我知道 GCD 和 Core Data 是这里的主要嫌疑人,但我不知道如何跟踪/调试它。

让我指出,当我仅将所有任务异步分派到队列时也会发生这种情况(到处使用dispatch_async )。这让我觉得这不仅仅是标准的僵局。

是否有任何可能性或提示我如何获得更多信息?一些额外的调试标志、仪器魔术技巧或构建设置等......

非常感谢任何关于可能原因的建议以及(或)如何以更好的方式实现 NSFetchedResultsController 的后台获取和后台导入的指针。

4

2 回答 2

3

NSFetchedResultsController我的第一个也是非常糟糕的错误是在后台队列中获取数据。

测试后发现,我对获取时间太敏感了。fetchData当我可以生成的最长获取时间实际上是一秒钟的时间时,我没有必要将执行置于后台线程,从而使与核心数据相关的代码过于复杂。这为非常小的性能增益(如果有的话)引入了太多的复杂性和不确定性。

我通过将 fetchData 执行和所有 NSFetchedResultsControllerDelegate 方法移到主线程(通过删除 GCD 代码来简化代码)来辞职。

完成此操作后,我不再需要mainMocDidSave:并从侦听 NSManagedObjectContextDidSaveNotificationfor 主线程上下文中注销。我还可以删除和取消注册 DATABASE_SAVED_WITH_CHANGES 通知发布。

这大大简化了“合并”机制,因为从此时起,只有后台线程上下文将其更改与主线程上下文合并(保存时)。我们称其为一种定向更改通知。

NSFetchedResultsControllerDelegate方法将在合并后拾取主线程上下文更改时自动触发。

另一个重要的事情是将dispatch_async更改为dispatch_sync

- (void)bgMocDidSave:(NSNotification *)saveNotification {

    // CDdb - bgMoc didsave - merging changes with main mainMoc
        // Previously was: dispatch_async
        // dispatch_sync in this place may prevent from overlapping merging in some cases (many blocks in background queue)
    dispatch_sync(dispatch_get_main_queue(), ^{

    [self.mainMoc mergeChangesFromContextDidSaveNotification:saveNotification];
     // !!! Extra notification NO needed anymore

    });
}

经验法则:简化和最小化线程和 NSManagedContexts 的数量。我体验到即使对于非常大的应用程序,拥有 2 个上下文也足够了

  1. importContext 在专用于 GCD 队列(必须是串行队列)中运行。请记住将其保存在队列块代码的末尾。
  2. mainConstext 在主 UI 线程(我称之为 READ 上下文 ;-)上操作,以便在为 UI(演示)提取数据时使用。
于 2012-09-19T21:31:10.240 回答
1

DATABASE_SAVED_WITH_CHANGES 通知看起来有点可疑:假设 bgMoc 保存。然后bgMocDidSave:触发并将更改与 mainMoc 合并,这很好。然后你触发一个通知,最终(我假设mainMocDidSave:在引发 DATABASE_SAVED_WITH_CHANGES 时触发)将更改合并回 bgMoc (这是起源的地方!)。这听起来对我来说不是正确的方法。此外,您可能想要检查bgMocDidSave:通知是否来自 bgMoc。如果 mainMoc 保存,则更改bgMocDidSave:也会触发。

于 2012-05-16T08:48:16.600 回答