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

Here's the setup:

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

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

这款应用程序只支持iOS5,所以它需要支持的最慢的设备是iPhone3GS.

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

Apple's Core Data Programming Guide: Efficiently Importing Data

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

iDeveloper TV - Core Data Performance

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

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在任何地方使用的上下文.它是master ManagedObjectContext的子级.我这样创建它:

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

backgroundContext-这个上下文是在负责将XML数据导入核心数据的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];
}];

Problem:问题是在重新加载视图之前,我的UI不会更新.

我有一个简单的UIViewController,它有一个使用NSFetchedResultsController馈送数据的UITableView.当导入过程完成时,NSFetchedResultsController See的父/主上下文没有变化,因此UI不会像我习惯看到的那样自动更新.如果我从堆栈中弹出UIViewController并再次加载它,所有数据都在那里.

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

我try 了以下应用程序挂起的方法:

- (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和后台MOC),都是"主"的父母

当你在一个子系统上保存时,它会将更改推送到父系统中.该MOC的其他子元素将在下次执行提取时看到数据...他们没有得到明确通知.

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

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

这应该行得通,所以我很好奇它挂在哪里我会注意到,您并没有以规范的方式在主MOC线程上运行(至少不适用于iOS5).

此外,您可能只对合并主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的子代.因此,它应该不必合并更改.相反,只需注意主机上的DidSave,然后重新获取!数据已经在您的父母中,就在等待您的请求.这是首先将数据放在父级中的好处之一.

另一个可以考虑的替代方案(我很想知道你的结果——这是大量的数据)…

与其让背景MOC成为主人的子元素,不如让它成为主人的子元素.

听好了.每次BG保存时,它都会自动推入主管道.现在,主程序必须调用save,然后主程序必须调用save,但所有这些操作都是移动指针.直到主服务器保存到磁盘.

该方法的优点在于,数据从后台MOC直接进入您的应用程序MOC(然后通过以进行保存).

传球有some分的处罚,但所有的重物在击中圆盘时都是在母盘上完成的.如果您使用PerformBlock在主服务器上执行这些保存,那么主线程只会发送请求,并立即返回.

请告诉我进展如何!

Ios相关问答推荐

找不到com.google.vr:sdk-base:1.180.0

底部导航栏在iOS上浮动

删除领域中的cartItem时出现问题

将 Riverpod 从 StateNotifier 修复为 NotifierProvider 以及应用程序生命周期监控

为什么 Firebase Messaging 配置对于 IOS native 和 Flutter iOS 不同?

CarPlay:现在播放alert 可能吗?

.frame() 修饰符的位置如何影响视图?

从 PHAssets 生成图像时,iOS 应用程序因内存问题而崩溃

iOS SwiftUI - 从以前的 struct 调用函数

SwiftUI 文本字段代理绑定代码忽略空格不起作用?

SwiftUI - 使用切换switch 切换键盘类型(默认/数字)?

光标未显示在 UITextView 中

如何获取 UICollectionViewCell 的矩形?

动画 UILabel 字体大小更改

Xcode / iOS模拟器:手动触发重大位置更改

如何在 Objective-C (iOS) 中的图像上写文本?

什么是强属性属性

如何找到 UILabel 的实际行数?

iOS应用程序每隔一次启动就会崩溃,找不到错误

在 SKScene 中设置按钮