核心数据背景上下文最佳实践

2023-11-25

我需要处理核心数据的大型导入任务。
假设我的核心数据模型如下所示:

Car
----
identifier 
type

我从服务器获取汽车信息 JSON 列表,然后我想将其与我的核心数据同步Car对象,含义:
如果是新车 -> 创建新的核心数据Car来自新信息的对象。
如果汽车已经存在 -> 更新核心数据Car目的。

因此,我想在后台执行此导入,而不阻塞 UI,同时使用滚动显示所有汽车的汽车表格视图。

目前我正在做这样的事情:

// create background context
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[bgContext setParentContext:self.mainContext];

[bgContext performBlock:^{
    NSArray *newCarsInfo = [self fetchNewCarInfoFromServer]; 

    // import the new data to Core Data...
    // I'm trying to do an efficient import here,
    // with few fetches as I can, and in batches
    for (... num of batches ...) {

        // do batch import...

        // save bg context in the end of each batch
        [bgContext save:&error];
    }

    // when all import batches are over I call save on the main context

    // save
    NSError *error = nil;
    [self.mainContext save:&error];
}];

但我不太确定我在这里做的是正确的事情,例如:

我用可以吗setParentContext ?
我看到一些像这样使用它的例子,但我看到其他不调用的例子setParentContext,相反,他们会做这样的事情:

NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
bgContext.persistentStoreCoordinator = self.mainContext.persistentStoreCoordinator;  
bgContext.undoManager = nil;

我不确定的另一件事是何时在主上下文中调用 save ,在我的示例中,我只是在导入结束时调用 save ,但我看到了使用以下内容的示例:

[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {
    NSManagedObjectContext *moc = self.managedObjectContext;
    if (note.object != moc) {
        [moc performBlock:^(){
            [moc mergeChangesFromContextDidSaveNotification:note];
        }];
    }
}];  

正如我之前提到的,我希望用户在更新时能够与数据进行交互,那么如果我在导入更改同一辆车时用户更改了汽车类型怎么办,我编写的方式安全吗?

UPDATE:

感谢@TheBasicMind很好的解释,我正在尝试实现选项A,所以我的代码看起来像这样:

这是 AppDelegate 中的核心数据配置:

AppDelegate.m  

#pragma mark - Core Data stack

- (void)saveContext {
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            DDLogError(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}  

// main
- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    _managedObjectContext.parentContext = [self saveManagedObjectContext];

    return _managedObjectContext;
}

// save context, parent of main context
- (NSManagedObjectContext *)saveManagedObjectContext {
    if (_writerManagedObjectContext != nil) {
        return _writerManagedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _writerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_writerManagedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _writerManagedObjectContext;
}  

这就是我的导入方法现在的样子:

- (void)import {
    NSManagedObjectContext *saveObjectContext = [AppDelegate saveManagedObjectContext];

    // create background context
    NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    bgContext.parentContext = saveObjectContext;

    [bgContext performBlock:^{
        NSArray *newCarsInfo = [self fetchNewCarInfoFromServer];

        // import the new data to Core Data...
        // I'm trying to do an efficient import here,
        // with few fetches as I can, and in batches
        for (... num of batches ...) {

            // do batch import...

            // save bg context in the end of each batch
            [bgContext save:&error];
        }

        // no call here for main save...
        // instead use NSManagedObjectContextDidSaveNotification to merge changes
    }];
}  

我还有以下观察者:

[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {

    NSManagedObjectContext *mainContext = self.managedObjectContext;
    NSManagedObjectContext *otherMoc = note.object;

    if (otherMoc.persistentStoreCoordinator == mainContext.persistentStoreCoordinator) {
        if (otherMoc != mainContext) {
            [mainContext performBlock:^(){
                [mainContext mergeChangesFromContextDidSaveNotification:note];
            }];
        }
    }
}];

对于第一次接触 Core Data 的人来说,这是一个极其令人困惑的话题。我并不是轻率地这么说,但根据经验,我有信心说苹果文档在这个问题上有些误导(如果你仔细阅读的话,实际上是一致的,但它们没有充分说明为什么合并数据仍然存在)在许多情况下,这是比依赖父/子上下文并简单地从子项保存到父项更好的解决方案)。

该文档给人留下了强烈的印象,父/子上下文是进行后台处理的新首选方式。然而,苹果公司却忽略了一些强烈的警告。首先,请注意,您获取到子上下文中的所有内容都首先通过其父上下文拉取。因此,最好限制在主线程上运行的主上下文的任何子级来处理(编辑)已在主线程上的 UI 中呈现的数据。如果您将其用于一般同步任务,您可能需要处理远远超出当前 UI 中显示范围的数据。即使您使用 NSPrivateQueueConcurrencyType,对于子编辑上下文,您也可能会在主上下文中拖动大量数据,这可能会导致性能不佳和阻塞。现在最好不要使主上下文成为用于同步的上下文的子上下文,因为除非您要手动执行此操作,否则它不会收到同步更新的通知,而且您将在某个计算机上执行可能长时间运行的任务。您可能需要对从作为主上下文子级的编辑上下文、通过主要联系人一直到数据存储的级联启动的保存做出响应。您必须手动合并数据,还可能跟踪需要在主上下文中失效并重新同步的内容。这不是最简单的模式。

Apple 文档没有明确说明的是,您很可能需要混合描述“旧”线程限制处理方式的页面上描述的技术和新的父子上下文处理方式。

您最好的选择可能是(我在这里给出一个通用解决方案,最佳解决方案可能取决于您的详细要求),将 NSPrivateQueueConcurrencyType 保存上下文作为最顶层的父级,直接保存到数据存储区。 [编辑:您不会直接在此上下文上做太多事情],然后为该保存上下文提供至少两个直接子级。一个是用于 UI 的 NSMainQueueConcurrencyType 主上下文 [编辑:最好遵守纪律,避免在此上下文中对数据进行任何编辑],另一个是 NSPrivateQueueConcurrencyType,您用于对数据进行用户编辑,并且(在附图中的选项 A)您的同步任务。

然后,将主上下文作为同步上下文生成的 NSManagedObjectContextDidSave 通知的目标,并将通知 .userInfo 字典发送到主上下文的 mergeChangesFromContextDidSaveNotification: 。

下一个要考虑的问题是在哪里放置用户编辑上下文(用户所做的编辑将反映回界面的上下文)。如果用户的操作始终仅限于对少量呈现数据进行编辑,那么使用 NSPrivateQueueConcurrencyType 再次将其作为主上下文的子级是您的最佳选择,并且最容易管理(保存将直接将编辑保存到主上下文中,并且如果如果您有一个 NSFetchedResultsController,则会自动调用适当的委托方法,以便您的 UI 可以处理更新控制器:didChangeObject:atIndexPath:forChangeType:newIndexPath:)(这又是选项 A)。

另一方面,如果用户操作可能导致处理大量数据,您可能需要考虑将其设为主上下文和同步上下文的另一个对等体,以便保存上下文具有三个直接子级。main, sync(私有队列类型)和edit(私有队列类型)。我在图中将这种安排显示为选项 B。

Similarly to the sync context you will need to [Edit: configure the main context to receive notifications] when data is saved (or if you need more granularity, when data is updated) and take action to merge the data in (typically using mergeChangesFromContextDidSaveNotification:). Note that with this arrangement, there is no need for the main context to ever call the save: method. enter image description here

要理解父/子关系,请采用选项 A:父子方法简单地意味着如果编辑上下文获取 NSManagedObjects,它们将首先被“复制到”(注册)保存上下文,然后是主上下文,最后是编辑上下文。您将能够对它们进行更改,然后当您在编辑上下文中调用 save: 时,更改将被保存只是到主要上下文。您必须在主上下文上调用 save: ,然后在保存上下文上调用 save: ,然后才能将它们写入磁盘。

当您从子级保存到父级时,会触发各种 NSManagedObject 更改和保存通知。例如,如果您使用获取结果控制器来管理 UI 的数据,那么将调用它的委托方法,以便您可以根据需要更新 UI。

一些后果:如果您在编辑上下文中获取对象和 NSManagedObject A,然后修改它并保存,因此修改将返回到主上下文。现在,您已针对主上下文和编辑上下文注册了修改后的对象。这样做是不好的风格,但您现在可以在主上下文中再次修改该对象,并且它现在将与存储在编辑上下文中的对象不同。如果您随后尝试对存储在编辑上下文中的对象进行进一步修改,则您的修改将与主上下文中的对象不同步,并且任何保存编辑上下文的尝试都会引发错误。

因此,使用像选项 A 这样的安排,尝试获取对象、修改它们、保存它们并重置编辑上下文是一个很好的模式(例如,使用运行循环的任何单次迭代(或在传递给 [editContext PerformBlock:]) 的任何给定块。最好还是遵守纪律并避免这样做any对主要上下文的编辑。 另外,重申一下,由于 main 上的所有处理都是主线程,因此如果您将大量对象提取到编辑上下文,则主上下文将执行提取处理在主线程上因为这些对象被迭代地从父上下文复制到子上下文。如果正在处理大量数据,这可能会导致 UI 无响应。因此,例如,如果您有大量托管对象,并且您有一个 UI 选项,将导致它们全部被编辑。在这种情况下,像选项 A 那样配置您的应用程序是一个坏主意。在这种情况下,选项 B 是更好的选择。

如果您不处理数千个对象,那么选项 A 可能就完全足够了。

顺便说一句,不必太担心您选择的选项。如果您需要更改为 B,那么从 A 开始可能是个好主意。进行此类更改比您想象的要容易,而且通常产生的后果也比您预期的要少。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

核心数据背景上下文最佳实践 的相关文章

随机推荐

  • Android 中的展开和折叠工具栏

    我正在借助折叠工具栏实现展开和折叠工具栏 但是当我的工具栏折叠时我陷入困境 我想显示不同的工具栏 我看过这样一段代码 但无法找到我的解决方案 我还看到了一位出色的开发人员的解决方案https github com saulmm Coordi
  • Response.Redirect() 与 Response.RedirectPermanent()

    我是 ASP Net 4 0 的新手 并且看到了一个名为Response RedirectPermanent 我查了一些文章 但我无法清楚地理解它们的实际含义和区别Response RedirectPermanent over Respon
  • CSS样式优先

    我在 CSS 声明优先级方面遇到问题 我的页面包含一个带有规则的外部 CSS 文件和一些内联 CSS 声明 这些声明应该覆盖该规则 据我了解 内联样式声明应该覆盖外部 CSS 声明 但是 当我在 Chrome 中查看页面时 表格的第二行显示
  • 泛型:什么是“构造函数约束”?

    我制作了一个自定义 TObjectList 后代 旨在保存基对象类的子类 它看起来像这样 interface TMyDataList
  • 如何在 java 8 中使用 lambda 表达式重写基类方法?

    Lambda 表达式必须转换为函数式接口 据我所知 他们无法扩展课程 但我想知道是否有办法获得类似的东西 I have java nio file SimpleFileVisitor
  • 如何在 WPF DataGrid 中定义自己的列?

    我有一个AutoGenerateColumnsWPF DataGrid 在代码隐藏中绑定到 LINQ to SQL 效果很好 但当我脱下AutoGenerateColumns并定义我自己的列 它告诉我 使用 ItemsSource 之前 项
  • 我可以写入 Azure 网站上的文件系统吗?

    我可以写入 Azure 网站上的文件系统吗 例如 从仪表板更新或安装 WordPress 中的插件 主题 AFAIK 这在 Heroku 上是不可能的 那么 Azure 网站呢 当然可以在Azure网站的文件系统上写入 但是 您的写入权限仅
  • 使用 SQLAlchemy ORM 高效更新数据库

    我正在启动一个新应用程序并考虑使用 ORM 特别是 SQLAlchemy 假设我的数据库中有一个列 foo 我想增加它 在直接 sqlite 中 这很简单 db sqlite3 connect mydata sqlitedb cur db
  • JSON 模式中的“$id”属性用法

    我在用着JSON 模式用于验证数据 我认为使用保留关键字 id 可能会导致我的模式出现错误 该字段的目的是指定另一个平台上的属性的 REMOTE ID 是什么 所以这就是 起源 ID 您能否告知 id 是什么以及我是否犯了一个严重错误并且该
  • 将 OpenCV 构建为静态库

    也许我遗漏了一些东西 但我无法构建 opencv 的静态库 Setup 库班图 12 04 海湾合作委员会4 6 3 使 3 81 cmake 2 8 7 opencv 2 4 6 1 现场最后可用 我手动完成所有工作 我尝试使用 cmak
  • 渲染方法 libgdx 中的增量值

    我在 Screen 类的渲染方法中检查了增量值 我看到它不是恒定的 任何人都可以说出它来自哪里以及它是什么吗 不同的屏幕尺寸有什么不同吗 如果是这样 我们怎样才能克服这个问题呢 我问这个是因为我的玩家跳跃取决于增量时间 有时它跳得太高 Th
  • 使用可选关键字参数定义类的 __init__ 方法的更好方法是什么?

    我希望班级做同样的事情如下 class Player def init self kwargs try self last name kwargs last name except pass try self first name kwar
  • 反应改变输入值 onChange

    这是我的搜索表单 js handleKeywordsChange必须处理输入keywords changes import React from react import ReactDOM from react dom class Sear
  • 使用相同的代码,TCPDF 比 FPDF 慢两倍

    我目前使用 FPDF 创建一些相当复杂的报告 并尝试升级到 TCPDF 但我发现通过 TCPDF 运行的相同代码大约慢两倍 因为我的 PDF 生成时间已经长达一分钟 所以我实在无法承受这种速度变慢的后果 但我真的很想利用一些 TCPDF 功
  • 返回受 UPDATE 语句影响的行数

    如何获取存储过程 SQL Server 2005 中受 UPDATE 查询影响的行数作为结果集 例如 CREATE PROCEDURE UpdateTables AS BEGIN SET NOCOUNT ON added to preven
  • RxJS 中的链接可观察量

    我正在学习 RxJS 和 Angular 2 假设我有一个包含多个异步函数调用的承诺链 这些调用取决于前一个函数的结果 如下所示 var promiseChain new Promise resolve reject gt setTimeo
  • 程序类型已存在:com.google.common.util.concurrent.ListenableFuture

    我刚刚将我的项目转换为 androidx 现在收到 程序类型已存在 错误com google common util concurrent ListenableFuture 我已经浏览了多个 stackoverflow 解决方案和一些 Gr
  • 如何使用 php cURL 发送请求负载?

    如何使用 php cURL 发送请求负载 我正在尝试将文件上传到免费托管文件的服务器 当将文件发送到curl时 我不接受它并重定向到错误页面 当我从Interfas执行此操作时 我可以看到文件如何发送到代码检查器中 我应该如何设置 cURL
  • 主干引导集合未正确初始化

    我有一个问题 确实很难注意到 因为在大多数情况下一切正常 只有当我尝试在集合初始化函数中操作数据时 我才发现了问题 主干文档位于http backbonejs org Collection constructor 如果你定义了一个初始化函数
  • 核心数据背景上下文最佳实践

    我需要处理核心数据的大型导入任务 假设我的核心数据模型如下所示 Car identifier type 我从服务器获取汽车信息 JSON 列表 然后我想将其与我的核心数据同步Car对象 含义 如果是新车 gt 创建新的核心数据Car来自新信