gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 18:33:32 25 4
gpt4 key购买 nike

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

Car
----
identifier
type

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

所以我想在不阻塞 UI 的情况下在后台执行此导入,并且在使用时滚动显示所有汽车的汽车表 View 。

目前我正在做这样的事情:
// 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];
}];
}
}];

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

更新:

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

这是 AppDelegate 中的 Core Data 配置:
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 的人来说,这是一个非常令人困惑的话题。我不是轻率地说,但根据经验,我有信心说 Apple 文档在这件事上有些误导(如果您仔细阅读它实际上是一致的,但它们并没有充分说明为什么合并数据仍然存在)在许多情况下,这是比依赖父/子上下文并简单地从子保存到父更好的解决方案)。

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

Apple 文档没有明确说明的是,您最有可能需要将描述“旧”线程限制做事方式的页面上描述的技术与新的父子上下文做事方式混合使用。

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

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

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

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

与同步上下文类似,您需要在保存数据时[编辑:配置主上下文以接收通知](或者如果您需要更多粒度,则在数据更新时)并采取措施合并数据(通常使用 mergeChangesFromContextDidSaveNotification: )。请注意,通过这种安排,主上下文不需要调用 save: 方法。
enter image description here

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

当您从子级保存到父级时,会触发各种 NSManagedObject 更改和保存通知。因此,例如,如果您使用 fetch results controller 来管理 UI 的数据,那么将调用它的委托(delegate)方法,以便您可以适本地更新 UI。

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

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

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

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

关于ios - 核心数据背景上下文最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24657437/

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