我最近重写了我的Core Data驱动数据库控制器,以使用Grand Central Dispatch来管理后台的提取和导入。控制器可以在2个NSManagedContext上运行:
NSManagedObjectContext *mainMoc
主线程的实例变量。此上下文仅供主线程或dipatch_get_main_queue()
全局队列快速访问UI使用。
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函数结束
bgMoc
使用mainMoc
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。我知道这种模式并不完全清理,原因有两个:
bgQueue
在暂停后不一定会触发同一个线程,但因为它是串行的,所以它应该没什么关系(从来没有2个线程尝试访问bgMoc
NSManagedObjectContext专用于它。)有这个缺陷的事件,这个方法在95%的应用程序运行时间上运行良好,直到......
和我的问题:
有时,非常随机的应用程序冻结但不会崩溃。它不会对任何触摸做出响应,并且使其恢复生存的唯一方法是完全重新启动它(切换回背景并没有帮助)。 抛出没有异常,并且没有任何内容打印到控制台(我在Xcode中为所有异常设置了Breakpoints)。
我曾尝试使用Instruments(特别是时间配置文件)对其进行调试,看看主线程上是否有什么问题,但没有任何内容出现。
我知道GCD和核心数据是这里的主要嫌疑人,但我不知道如何跟踪/调试它。
让我指出,当我仅异步地将所有任务分派到队列时(在所有地方使用 dispatch_async ),也会发生这种情况。这让我觉得它不仅仅是标准的死锁。
是否有任何可能或提示我如何获得更多信息?一些额外的调试标志,乐器魔法技巧或构建设置等......
非常感谢关于可能是什么原因的任何建议,以及(或)指向如何以更好的方式实现NSFetchedResultsController和后台导入的后台获取的指针。
答案 0 :(得分:3)
我的第一个也是非常糟糕的错误是在后台队列中获取NSFetchedResultsController
的数据。
经过测试后,我对抓取时间过于敏感了。我不必要将fetchData
执行放回到后台线程,使得核心数据相关代码过于复杂,因为我可以生成的最长获取时间实际上是一秒钟的分裂。这引入了太多的复杂性和不确定性,以获得非常小的性能增益(如果有的话)。
我通过将fetchData执行和所有NSFetchedResultsControllerDelegate方法移动到主线程(通过删除GCD代码简化代码)来辞职。
完成此操作后,我不再需要mainMocDidSave:
并且在注册主线程上下文的NSManagedObjectContextDidSaveNotification
时取消注册。
我还可以删除和取消注册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
});
}
经验法则:SIMPLIFY和MINIMIZE线程数量和NSManagedContexts。 我经历过即使对于非常大的应用程序也有2个上下文:
答案 1 :(得分:1)
DATABASE_SAVED_WITH_CHANGES通知看起来有点可疑:假设bgMoc保存。然后bgMocDidSave:
触发并将更改与mainMoc合并,这很好。然后你发出一个通知,最后(我假设{DATABASE_SAVED_WITH_CHANGES被引发时会发出mainMocDidSave:
})将更改合并到bgMoc(这是源于哪里!)。这对我来说听起来不是正确的做法。
您也可以在bgMocDidSave:
签入该通知来自bgMoc。如果mainMoc保存,那么bgMocDidSave:
也会触发更改。