在iOS 5上实现快速高效的核心数据导入

在iOS 5上实现快速高效的核心数据导入,ios,core-data,nsfetchedresultscontroller,nsmanagedobjectcontext,Ios,Core Data,Nsfetchedresultscontroller,Nsmanagedobjectcontext,问题:如何让我的子上下文看到父上下文上持久的更改,从而触发我的NSFetchedResultsController更新UI 以下是设置: 你有一个应用程序可以下载和添加大量XML数据(大约200万条记录,每条记录的大小大约相当于一段正常的文本),而.sqlite文件的大小大约为500 MB。将此内容添加到核心数据需要时间,但您希望用户能够在数据以增量方式加载到数据存储时使用应用程序。它必须是不可见的,用户无法察觉大量数据正在移动,因此没有挂起,没有抖动:像黄油一样滚动。不过,该应用程序更有用,添

问题:如何让我的子上下文看到父上下文上持久的更改,从而触发我的NSFetchedResultsController更新UI

以下是设置:

你有一个应用程序可以下载和添加大量XML数据(大约200万条记录,每条记录的大小大约相当于一段正常的文本),而.sqlite文件的大小大约为500 MB。将此内容添加到核心数据需要时间,但您希望用户能够在数据以增量方式加载到数据存储时使用应用程序。它必须是不可见的,用户无法察觉大量数据正在移动,因此没有挂起,没有抖动:像黄油一样滚动。不过,该应用程序更有用,添加的数据越多,因此我们不能永远等待数据添加到核心数据存储。在代码中,这意味着我真的希望在导入代码中避免这样的代码:

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
该应用程序是iOS5,所以它需要支持的最慢设备是iPhone3GS

以下是迄今为止我用于开发当前解决方案的资源:

  • 使用自动释放池来降低内存
  • 关系成本。导入平面,然后在末尾修补关系
  • 不要质疑你是否能帮助它,它会以O(n^2)的方式减慢速度
  • 批量导入:保存、重置、排空和重复
  • 在导入时关闭撤消管理器

  • 使用3种上下文:主上下文、主上下文和限制上下文类型

  • 使用performBlock在其他队列上运行save可以加快速度
  • 加密会减慢速度,如果可以,请将其关闭

  • 您可以通过给当前运行循环留出时间来减慢导入速度, 因此,用户感觉一切都很顺利
  • 示例代码证明,可以执行大型导入并保持UI响应,但速度不如3个上下文和异步保存到磁盘
我当前的解决方案 我有3个NSManagedObjectContext实例:

masterManagedObjectContext-这是具有NSPersistentStoreCoordinator并负责保存到磁盘的上下文。我这样做是为了我的保存可以是异步的,因此速度非常快。我在发布时创建它,如下所示:

masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
mainManagedObjectContext-这是UI在任何地方使用的上下文。它是masterManagedObjectContext的子对象。我是这样创建的:

masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
backgroundContext-此上下文在负责将XML数据导入核心数据的my NSOperation子类中创建。我在操作的主方法中创建它,并将其链接到主上下文

backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
这实际上非常非常快。仅仅通过做这个3上下文设置,我就能够将我的导入速度提高10倍以上!老实说,这很难相信。(此基本设计应为标准核心数据模板的一部分…)

在导入过程中,我保存了两种不同的方式。我在后台上下文中每保存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];
}

您可能也应该大步保存主MOC。让主运行中心等到最后再保存是没有意义的。它有自己的线程,也有助于降低内存

你写道:

然后在导入过程结束时,我保存在主/父目录上 表面上,将修改推给另一个孩子的上下文 上下文包括主上下文:

[masterManagedObjectContext performBlock:^{
   NSError *parentContextError = nil;
   BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
在您的配置中,您有两个子项(主MOC和后台MOC),都是“主”的父项

在子级上保存时,它会将更改向上推送到父级。MOC的其他子级将在下次执行提取时看到数据。。。他们没有得到明确通知

因此,当BG保存时,其数据被推送到主服务器。但是,请注意,在MASTER保存之前,这些数据都不在磁盘上。此外,在主设备保存到磁盘之前,任何新项目都不会获得永久ID

在您的场景中,通过在DidSave通知期间合并主存储,将数据拉入主MOC

这应该是可行的,所以我很好奇它“挂起”在哪里。我会注意到,您没有以规范的方式在主MOC线程上运行(至少不是在iOS 5上)

此外,您可能只对合并主MOC的更改感兴趣(尽管您的注册看起来只是为了合并主MOC的更改)。如果我在did save通知上使用更新,我会这样做

- (void)contextChanged:(NSNotification*)notification {
    // Only interested in merging from master into main.
    if ([notification object] != masterManagedObjectContext) return;

    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        // NOTE: our MOC should not be updated, but we need to reload the data as well
    }];
}
现在,关于绞刑,你真正的问题是什么。。。您将显示两个不同的调用以保存在主机上。第一个在它自己的performBlock中得到很好的保护,但第二个没有(尽管您可能在performBlock中调用saveMasterContext

不过,我也会更改此代码

- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    // Make sure the master runs in it's own thread...
    [masterManagedObjectContext performBlock:^{
        NSError *error = nil;
        BOOL saveSuccess = [masterManagedObjectContext save:&error];
        // Handle error...
        [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
    }];
}
但是,请注意,MAIN是MASTER的子级。因此,它不必合并更改。相反,只需注意MASTER上的DidSave,然后重新蚀刻即可!数据已经存储在父级中,正在等待您的更改