问题:如何让我的子上下文看到父上下文中持续存在的更改,以便它们触发我的 NSFetchedResultsController 来更新 UI?
这是设置:
您有一个应用程序,它下载并添加大量 XML 数据(大约 200 万条记录,每条记录大致相当于一段普通文本的大小)。.sqlite 文件的大小约为 500 MB。将此内容添加到 Core Data 需要时间,但您希望用户能够在数据以增量方式加载到数据存储时使用该应用程序。大量数据在四处移动,因此用户必须不可见且无法察觉,因此不会出现挂起和抖动:像黄油一样滚动。尽管如此,应用程序更有用,添加的数据越多,所以我们不能永远等待数据被添加到核心数据存储中。在代码中,这意味着我真的很想在导入代码中避免这样的代码:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
该应用程序仅适用于 iOS 5,因此它需要支持的最慢设备是 iPhone 3GS。
以下是我迄今为止用于开发当前解决方案的资源:
- 使用自动释放池来降低内存
- 关系成本。导入平面,然后在最后修补关系
- 不要询问您是否可以帮助它,它会以 O(n^2) 的方式减慢速度
- 批量导入:保存、重置、排空和重复
- 导入时关闭撤消管理器
- 使用 3 个上下文:主上下文类型、主要上下文类型和限制上下文类型
iDeveloper TV - 适用于 Mac、iPhone 和 iPad 的核心数据更新
- 使用 performBlock 在其他队列上运行保存会使事情变得更快。
- 加密会减慢速度,如果可以,请关闭它。
Marcus Zarra 在 Core Data 中导入和显示大型数据集
- 您可以通过为当前的运行循环留出时间来减慢导入速度,从而让用户感觉顺畅。
- 示例代码证明可以进行大量导入并保持 UI 响应,但不如使用 3 个上下文和异步保存到磁盘快。
我目前的解决方案
我有 3 个 NSManagedObjectContext 实例:
masterManagedObjectContext - 这是具有 NSPersistentStoreCoordinator 并负责保存到磁盘的上下文。我这样做是为了让我的保存可以是异步的,因此非常快。我在启动时创建它,如下所示:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - 这是 UI 到处使用的上下文。它是 masterManagedObjectContext 的子级。我这样创建它:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext - 这个上下文是在我的 NSOperation 子类中创建的,它负责将 XML 数据导入核心数据。我在操作的主要方法中创建它并将其链接到那里的主上下文。
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
这实际上非常非常快。只需执行这 3 个上下文设置,我就能将导入速度提高 10 倍以上!老实说,这很难相信。(这个基本设计应该是标准核心数据模板的一部分......)
在导入过程中,我保存了 2 种不同的方式。我在后台上下文中保存的每 1000 个项目:
BOOL saveSuccess = [backgroundContext save:&error];
然后在导入过程结束时,我保存主/父上下文,表面上,它将修改推送到其他子上下文,包括主上下文:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
问题:问题是我的 UI 在我重新加载视图之前不会更新。
我有一个简单的 UIViewController 和一个 UITableView,它使用 NSFetchedResultsController 提供数据。导入过程完成后,NSFetchedResultsController 看到父/主上下文没有任何变化,因此 UI 不会像我以前看到的那样自动更新。如果我将 UIViewController 从堆栈中弹出并再次加载它,所有数据都在那里。
问题:如何让我的子上下文看到父上下文中持续存在的更改,以便它们触发我的 NSFetchedResultsController 来更新 UI?
我尝试了以下只是挂起应用程序的方法:
- (void)saveMasterContext {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
NSError *error = nil;
BOOL saveSuccess = [masterManagedObjectContext save:&error];
[notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == mainManagedObjectContext) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}