gpt4 book ai didi

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

转载 作者:IT王子 更新时间:2023-10-29 07:29:27 25 4
gpt4 key购买 nike

问题 :如何让我的子上下文看到父上下文上的更改,以便它们触发我的 NSFetchedResultsController 来更新 UI?

这是设置:

您有一个应用程序,可以下载并添加大量 XML 数据(大约 200 万条记录,每个记录大约相当于一段普通文本的大小)。 .sqlite 文件大小变为大约 500 MB。将此内容添加到 Core Data 需要时间,但您希望用户能够在数据以增量方式加载到数据存储中时使用该应用程序。大量数据在四处移动,这对用户来说必须是不可见和不可察觉的,因此不会挂起,不会抖动:像黄油一样滚动。尽管如此,该应用程序更有用,添加到其中的数据越多,因此我们不能永远等待数据添加到 Core Data 存储中。在代码中,这意味着我真的很想避免在导入代码中使用这样的代码:

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

该应用程序仅适用于 iOS 5,因此它需要支持的最慢设备是 iPhone 3GS。

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

Apple's Core Data Programming Guide: Efficiently Importing Data
  • 使用自动释放池降低内存
  • 关系成本。导入flat,最后补上关系
  • 不要询问您是否可以帮助它,它会以 O(n^2) 的方式减慢速度
  • 批量导入:保存、重置、排空和重复
  • 在导入时关闭撤消管理器

  • iDeveloper TV - Core Data Performance
  • 使用 3 个上下文:Master、Main 和 Confinement 上下文类型

  • iDeveloper TV - Core Data for Mac, iPhone & iPad Update
  • 使用 performBlock 在其他队列上运行保存使事情变得更快。
  • 加密会减慢速度,如果可以,请将其关闭。

  • Importing and Displaying Large Data Sets in Core Data by Marcus Zarra
  • 您可以通过为当前运行循环留出时间来减慢导入速度,
    所以用户感觉很顺利。
  • 示例代码证明可以进行大量导入并保持 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];

    背景上下文 - 这个上下文是在我的 NSOperation 子类中创建的,它负责将 XML 数据导入到 Core Data 中。我在操作的 main 方法中创建它并将它链接到那里的主上下文。
    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 在我重新加载 View 之前不会更新。

    我有一个带有 UITableView 的简单 UIViewController,它使用 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。让 MOC 等到最后保存是没有意义的。它有自己的线程,它也有助于减少内存。

    你写道:

    Then at the end of the import process, I save on the master/parent context which, ostensibly, pushes modifications out to the other child contexts including the main context:



    在您的配置中,您有两个子项(主 MOC 和后台 MOC),它们都是“master”的父级。

    当您保存子项时,它会将更改推送到父项中。该 MOC 的其他子项将在下次执行提取时看到数据......他们没有被明确通知。

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

    在您的场景中,您通过在 DidSave 通知期间从 MASTER 保存合并将数据拉入 MAIN MOC。

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

    此外,您可能只对合并来自主 MOC 的更改感兴趣(尽管您的注册看起来仅用于此目的)。如果我要使用 update-on-did-save-notification,我会这样做...
    - (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,然后重新获取!数据已经在你的父级中了,就等着你去索取。这是首先将数据放在父级中的好处之一。

    另一个要考虑的替代方案(我很想知道你的结果——这是很多数据)......

    不要让背景 MOC 成为 MASTER 的子级,而是让它成为 MAIN 的子级。

    得到这个。每次 BG 保存时,它都会自动被插入 MAIN。现在,MAIN 必须调用 save,然后 master 必须调用 save,但所有这些都在移动指针……直到 master 保存到磁盘。

    该方法的美妙之处在于数据从后台 MOC 直接进入您的应用程序 MOC(然后通过以保存)。

    传递会有一些惩罚,但是当它碰到磁盘时,所有繁重的工作都在 MASTER 中完成。如果你用 performBlock 踢掉 master 上的那些保存,那么主线程只会发送请求,并立即返回。

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

    关于ios - 在 iOS 5 上实现快速高效的核心数据导入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10542097/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com