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

问题 :如何获取我的子上下文以查看父上下文中保留的更改,以便触发我的NSFetchedResultsController更新UI?

这是设置:

你已经有一个应用程序下载并添加大量的XML数据(大约200万条logging,每个logging的大小约为普通段落的文本大小).sqlite文件的大小约为500 MB。 将这些内容添加到Core Data中需要时间,但是您希望用户能够在数据逐渐加载到数据存储中的同时使用该应用程序。 对于用户来说,它是不可见的,不可见的,因为大量的数据正在被移动,所以没有挂起,没有抖动:滚动像黄油。 不过,应用程序更有用,数据添加的越多,所以我们不能永远等待将数据添加到Core Data存储区。 在代码中,这意味着我真的想在导入代码中避免这样的代码:

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]]; 

该应用程序只有iOS 5,所以它需要支持的最慢的设备是iPhone 3GS。

以下是我迄今用来开发当前解决scheme的资源:

苹果的核心数据编程指南:有效地导入数据

  • 使用自动释放池来减less内存
  • 关系成本。 导入单位,然后在最后修补关系
  • 不要询问是否可以帮助它,它会以O(n ^ 2)的方式减慢速度
  • 批量导入:保存,重置,排出和重复
  • closures导入时的撤消pipe理器

iDeveloper电视 – 核心数据性能

  • 使用3个上下文:Master,Main和Confinement上下文types

iDeveloper TV – Mac,iPhone和iPad更新的核心数据

  • 使用performBlock在其他队列上运行保存会使事情变得更快。
  • encryption会减慢速度,如果可以的话关掉它。

导入和显示核心数据中的大型数据集Marcus Zarra

  • 您可以通过给当前运行循环留出时间来减慢导入速度,这样用户就能感觉到stream畅。
  • 示例代码certificate,可以执行大量的导入操作并保持UI的响应速度,但速度不及3个上下文和asynchronous存储到磁盘的速度。

我当前的解决scheme

我有3个NSManagedObjectContext实例:

masterManagedObjectContext – 这是具有NSPersistentStoreCoordinator并负责保存到磁盘的上下文。 我这样做,所以我的保存可以是asynchronous的,因此非常快。 我这样创build它:

 masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [masterManagedObjectContext setPersistentStoreCoordinator:coordinator]; 

mainManagedObjectContext – 这是UI在各处使用的上下文。 它是masterManagedObjectContext的子项。 我创build它是这样的:

 mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; [mainManagedObjectContext setUndoManager:nil]; [mainManagedObjectContext setParentContext:masterManagedObjectContext]; 

backgroundContext – 这个上下文是在我的NSOperation子类中创build的,它负责将XML数据导入到Core Data中。 我在操作的主要方法中创build它并将其链接到主环境。

 backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType]; [backgroundContext setUndoManager:nil]; [backgroundContext setParentContext:masterManagedObjectContext]; 

这实际上非常非常快, 只要做了这3个上下文设置,我就可以提高10倍以上的import速度! 老实说,这很难相信。 (这个基本的devise应该是标准核心数据模板的一部分…)

在导入过程中,我保存2种不同的方式。 每1000个我保存在背景上的项目:

 BOOL saveSuccess = [backgroundContext save:&error]; 

然后在导入过程结束时,我保存在主/父上下文中,表面上将修改推送到其他子上下文,包括主上下文​​:

 [masterManagedObjectContext performBlock:^{ NSError *parentContextError = nil; BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError]; }]; 

问题 :问题是我的用户界面不会更新,直到我重新加载视图。

我有一个简单的UIViewController与UITableView正在被馈送数据使用NSFetchedResultsController。 导入过程完成后,NSFetchedResultsController看到从父/母上下文没有任何更改,所以用户界面不会像我以前看到的那样自动更新。 如果我popupUIViewController离开堆栈,并再次加载所有的数据在那里。

问题 :如何获取我的子上下文以查看父上下文中保留的更改,以便触发我的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]; } 

你也应该大步拯救主M​​OC。 没有意识到这个MOC等到最后保存。 它有自己的线程,它也有助于保持内存不变。

你写了:

然后在导入过程结束时,我保存在主/父上下文中,表面上将修改推送到其他子上下文,包括主上下文​​:

在你的configuration中,你有两个孩子(主要是MOC和背景MOC),都是“主人”的父亲。

当您保存在孩子身上时,会将更改推送给父母。 该交通部的其他子女将在下一次执行取数时看到数据……他们没有明确通知。

所以,当BG保存时,其数据被推送到MASTER。 但请注意,在MASTER保存之前,这些数据都不在磁盘上。 而且,在MASTER保存到磁盘之前,任何新项目都不会获得永久ID。

在您的情况下,您将在DidSave通知期间通过合并MASTER保存将数据拖入主MOC。

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

此外,您可能只对合并来自主MOC的更改感兴趣(尽pipe您的注册看起来只是为了这一点)。 如果我要使用更新的保存通知,我会这样做…

 - (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中有很好的保护,但是第二个没有(尽pipe你可能会在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的一个孩子。 所以,它不应该合并更改。 相反,只要注意主人的DidSave,然后重新提取! 这些数据已经在您的父母身上,只是等待您的要求。 这是首先将数据放在父项中的好处之一。

另一个可供select的方法(我希望了解你的结果 – 这是很多数据)…

不要让背景MOC成为MASTER的孩子,而要成为MAIN的孩子。

得到这个。 每次BG保存时,它都会自动进入MAIN。 现在,主要调用保存,然后主要调用保存,但所有这些正在做的是移动指针…直到主保存到磁盘。

该方法的优点是数据从后台MOC直接进入应用程序MOC(然后通过保存)。

传递有一些惩罚,但是所有的繁重工作都会在MASTER中完成。 如果你用performBlock把这些保存在master上,那么主线程就发送请求,并立即返回。

请让我知道它是怎么回事!