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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24657437/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 01:17:54  来源:igfitidea点击:

Core Data background context best practice

iosobjective-ccore-datansmanagedobjectcontext

提问by Eyal

I have a large import task I need to do with core data.
Let say my core data model look like this:

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

Car
----
identifier 
type

I fetch a list of car info JSON from my server and then I want to sync it with my core data Carobject, meaning:
If its a new car -> create a new Core Data Carobject from the new info.
If the car already exists -> update the Core Data Carobject.

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

So I want to do this import in background without blocking the UI and while the use scrolls a cars table view that present all the cars.

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

Currently I'm doing something like this:

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

// 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];
}];

But I'm not really sure I'm doing the right thing here, for example:

但我不确定我在这里做的对不对,例如:

Is it ok that I use setParentContext?
I saw some examples that use it like this, but I saw other examples that don't call setParentContext, instead they do something like this:

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

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

Another thing that I'm not sure is when to call save on the main context, In my example I just call save in the end of the import, but I saw examples that uses:

我不确定的另一件事是何时在主上下文中调用 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];
        }];
    }
}];  

As I mention before, I want the user to be able to interact with the data while updating, so what if I the user change a car type while the import change the same car, is the way I wrote it safe?

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

UPDATE:

更新:

Thanks to @TheBasicMind great explanation I'm trying to implement option A, so my code looks something like:

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

This is the Core Data configuration in AppDelegate:

这是 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;
}  

And this is how my import method looks like now:

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

- (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
    }];
}  

And I also have the following observer:

我还有以下观察者:

[[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];
            }];
        }
    }
}];

回答by TheBasicMind

This is an extremely confusing topic for people approaching Core Data for the first time. I don't say this lightly, but with experience, I am confident in saying the Apple documentation is somewhat misleading on this matter (it is in fact consistent if you read it very carefully, but they don't adequately illustrate why merging data remains in many instances a better solution than relying on parent/child contexts and simply saving from a child to the parent).

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

The documentation gives the strong impression parent/child contexts are the new preferred way to do background processing. However Apple neglect to highlight some strong caveats. Firstly, be aware that everything you fetch into your child context is first pulled through it's parent. Therefore it is best to limit any child of the main context running on the main thread to processing (editing) data that has already been presented in the UI on the main thread. If you use it for general synchronisation tasks it is likely you will be wanting to process data which extends far beyond the bounds of what you are currently displaying in the UI. Even if you use NSPrivateQueueConcurrencyType, for the child edit context, you will potentially be dragging a large amount of data through the main context and that can lead to bad performance and blocking. Now it is best not to make the main context a child of the context you use for synchronisation, because it won't be notified of synchronisation updates unless you are going to do that manually, plus you will be executing potentially long running tasks on a context you might need to be responsive to saves initiated as a cascade from the edit context that is a child of your main context, through the main contact and down to the data store. You will have to either manually merge the data and also possibly track what needs to be invalidated in the main context and re-sync. Not the easiest pattern.

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

What the Apple documentation does not make clear is that you are most likely to need a hybrid of the techniques described on the pages describing the "old" thread confinement way of doing things, and the new Parent-Child contexts way of doing things.

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

Your best bet is probably (and I'm giving a generic solution here, the best solution may be dependent on your detailed requirements), to have a NSPrivateQueueConcurrencyType save context as the topmost parent, which saves directly to the datastore. [Edit: you won't be doing very much directly on this context], then give that save context at least two direct children. One your NSMainQueueConcurrencyType main context you use for the UI [Edit: it's best to be disciplined and avoid ever doing any editing of the data on this context], the other a NSPrivateQueueConcurrencyType, you use to do user edits of the data and also (in option A in the attached diagram) your synchronisation tasks.

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

Then you make the main context the target of the NSManagedObjectContextDidSave notification generated by the sync context, and send the notifications .userInfo dictionary to the main context's mergeChangesFromContextDidSaveNotification:.

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

The next question to consider is where you put the user edit context (the context where edits made by the user get reflected back into the interface). If the user's actions are always confined to edits on small amounts of presented data, then making this a child of the main context again using the NSPrivateQueueConcurrencyType is your best bet and easiest to manage (save will then save edits directly into the main context and if you have an NSFetchedResultsController, the appropriate delegate method will be called automatically so your UI can process the updates controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:) (again this is option A).

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

If on the other hand user actions might result in large amounts of data being processed, you might want to consider making it another peer of the main context and the sync context, such that the save context has three direct children. main, sync(private queue type) and edit(private queue type). I've shown this arrangement as option B on the diagram.

另一方面,如果用户操作可能导致处理大量数据,您可能需要考虑使其成为主上下文和同步上下文的另一个对等体,以便保存上下文具有三个直接子级。mainsync(私有队列类型)和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

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

To understand parent/child relationships, take Option A: The parent child approach simply means if the edit context fetches NSManagedObjects, they will be "copied into" (registered with) first the save context, then the main context, then finally edit context. You will be able to make changes to them, then when you call save: on the edit context, the changes will saved just to the main context. You would have to call save: on the main context and then call save: on the save context before they will be written out to disk.

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

When you save from a child, up to a parent, the various NSManagedObject change and save notifications are fired. So for example if you are using a fetch results controller to manage your data for your UI, then it's delegate methods will be called so you can update the UI as appropriate.

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

Some consequences: If you fetch object and NSManagedObject A on the edit context, then modify it, and save, so the modifications are returned to the main context. You now have the modified object registered against both the main and the edit context. It would be bad style to do so, but you could now modify the object again on the main context and it will now be different from the object as it is stored in the edit context. If you then try to make further modifications to the object as stored in the edit context, your modifications will be out of sync with the object on the main context, and any attempt to save the edit context will raise an error.

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

For this reason, with an arrangement like option A, it is a good pattern to try to fetch objects, modify them, save them and reset the edit context (e.g. [editContext reset] with any single iteration of the run-loop (or within any given block passed to [editContext performBlock:]). It is also best to be disciplined and avoid ever doing anyedits on the main context. Also, to re-iterate, since all processing on main is the main thread, if you fetch lots of objects to the edit context, the main context will be doing it's fetch processing on the main threadas those objects are being copied down iteratively from parent to child contexts. If there is a lot of data being processed, this can cause unresponsiveness in the UI. So if, for example you have a large store of managed objects, and you have a UI option that would result in them all being edited. It would be a bad idea in this case to configure your App like option A. In such a case option B is a better bet.

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

If you aren't processing thousands of objects, then option A may be entirely sufficient.

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

BTW don't worry too much over which option you select. It might be a good idea to start with A and if you need to change to B. It's easier than you might think to make such a change and usually has fewer consequences than you might expect.

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

回答by malhal

Firstly, parent/child context are not for background processing. They are for atomic updates of related data that might be created in multiple view controllers. So if the last view controller is cancelled, the child context can be thrown away with no adverse affects on the parent. This is fully explained by Apple at the bottom of this answer at [^1]. Now that is out of the way and you haven't fallen for the common mistake, you can focus on how to properly do background Core Data.

首先,父/子上下文不用于后台处理。它们用于可能在多个视图控制器中创建的相关数据的原子更新。因此,如果取消最后一个视图控制器,则可以丢弃子上下文,而不会对父级产生不利影响。Apple 在 [^1] 的此答案底部对此进行了充分解释。既然已经解决了并且您还没有陷入常见的错误,您可以专注于如何正确地做后台 Core Data。

Create a new persistent store coordinator (no longer needed on iOS 10 see update below) and a private queue context. Listen for the save notification and merge the changes into the main context (on iOS 10 the context has a property to do this automatically)

创建一个新的持久存储协调器(在 iOS 10 上不再需要,请参阅下面的更新)和一个私有队列上下文。侦听保存通知并将更改合并到主上下文中(在 iOS 10 上,上下文具有自动执行此操作的属性)

For a sample by Apple see "Earthquakes: Populating a Core Data Store Using a Background Queue" https://developer.apple.com/library/mac/samplecode/Earthquakes/Introduction/Intro.htmlAs you can see from the revision history on 2014-08-19 they added "New sample code that shows how to use a second Core Data stack to fetch data on a background queue."

有关 Apple 的示例,请参阅“地震:使用后台队列填充核心数据存储” https://developer.apple.com/library/mac/samplecode/Earthquakes/Introduction/Intro.html正如您从修订历史中看到的那样在 2014 年 8 月 19 日,他们添加了“新示例代码,展示了如何使用第二个核心数据堆栈来获取后台队列上的数据。”

Here is that bit from AAPLCoreDataStackManager.m:

这是来自 AAPCoreDataStackManager.m 的那一点:

// Creates a new Core Data stack and returns a managed object context associated with a private queue.
- (NSManagedObjectContext *)createPrivateQueueContext:(NSError * __autoreleasing *)error {

    // It uses the same store and model, but a new persistent store coordinator and context.
    NSPersistentStoreCoordinator *localCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[AAPLCoreDataStackManager sharedManager].managedObjectModel];

    if (![localCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil
                                                  URL:[AAPLCoreDataStackManager sharedManager].storeURL
                                              options:nil
                                                error:error]) {
        return nil;
    }

    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [context performBlockAndWait:^{
        [context setPersistentStoreCoordinator:localCoordinator];

        // Avoid using default merge policy in multi-threading environment:
        // when we delete (and save) a record in one context,
        // and try to save edits on the same record in the other context before merging the changes,
        // an exception will be thrown because Core Data by default uses NSErrorMergePolicy.
        // Setting a reasonable mergePolicy is a good practice to avoid that kind of exception.
        context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;

        // In OS X, a context provides an undo manager by default
        // Disable it for performance benefit
        context.undoManager = nil;
    }];
    return context;
}

And in AAPLQuakesViewController.m

在 AAPLQuakesViewController.m 中

- (void)contextDidSaveNotificationHandler:(NSNotification *)notification {

    if (notification.object != self.managedObjectContext) {

        [self.managedObjectContext performBlock:^{
            [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
        }];
    }
}

Here is the full description of how the sample is designed:

以下是示例设计的完整说明:

Earthquakes: Using a "private" persistent store coordinator to fetch data in background

地震:使用“私有”持久存储协调器在后台获取数据

Most applications that use Core Data employ a single persistent store coordinator to mediate access to a given persistent store. Earthquakes shows how to use an additional "private" persistent store coordinator when creating managed objects using data retrieved from a remote server.

大多数使用 Core Data 的应用程序都使用单个持久存储协调器来调解对给定持久存储的访问。Earthquakes 展示了在使用从远程服务器检索的数据创建托管对象时如何使用额外的“私有”持久存储协调器。

Application Architecture

应用架构

The application uses two Core Data "stacks" (as defined by the existence of a persistent store coordinator). The first is the typical "general purpose" stack; the second is created by a view controller specifically to fetch data from a remote server (As of iOS 10 a second coordinator is no longer needed, see update at bottom of answer).

该应用程序使用两个核心数据“堆栈”(由持久存储协调器的存在定义)。第一个是典型的“通用”堆栈;第二个是由视图控制器创建的,专门用于从远程服务器获取数据(从 iOS 10 开始,不再需要第二个协调器,请参阅答案底部的更新)。

The main persistent store coordinator is vended by a singleton "stack controller" object (an instance of CoreDataStackManager). It is the responsibility of its clients to create a managed object context to work with the coordinator[^1]. The stack controller also vends properties for the managed object model used by the application, and the location of the persistent store. Clients can use these latter properties to set up additional persistent store coordinators to work in parallel with the main coordinator.

主要的持久存储协调器由一个单独的“堆栈控制器”对象(CoreDataStackManager 的一个实例)提供。它的客户有责任创建一个托管对象上下文以与协调器一起工作[^1]。堆栈控制器还提供应用程序使用的托管对象模型的属性,以及持久存储的位置。客户端可以使用后面的这些属性来设置额外的持久存储协调器以与主协调器并行工作。

The main view controller, an instance of QuakesViewController, uses the stack controller's persistent store coordinator to fetch quakes from the persistent store to display in a table view. Retrieving data from the server can be a long-running operation which requires significant interaction with the persistent store to determine whether records retrieved from the server are new quakes or potential updates to existing quakes. To ensure that the application can remain responsive during this operation, the view controller employs a second coordinator to manage interaction with the persistent store. It configures the coordinator to use the same managed object model and persistent store as the main coordinator vended by the stack controller. It creates a managed object context bound to a private queue to fetch data from the store and commit changes to the store.

主视图控制器是 QuakesViewController 的一个实例,它使用堆栈控制器的持久存储协调器从持久存储中获取地震以显示在表视图中。从服务器检索数据可能是一个长时间运行的操作,需要与持久存储进行大量交互,以确定从服务器检索的记录是新地震还是对现有地震的潜在更新。为了确保应用程序可以在此操作期间保持响应,视图控制器采用第二个协调器来管理与持久存储的交互。它将协调器配置为使用与堆栈控制器提供的主协调器相同的托管对象模型和持久存储。

[^1]: This supports the "pass the baton" approach whereby—particularly in iOS applications—a context is passed from one view controller to another. The root view controller is responsible for creating the initial context, and passing it to child view controllers as/when necessary.

[^1]:这支持“传递接力棒”的方法——尤其是在 iOS 应用程序中——上下文从一个视图控制器传递到另一个视图控制器。根视图控制器负责创建初始上下文,并在必要时将其传递给子视图控制器。

The reason for this pattern is to ensure that changes to the managed object graph are appropriately constrained. Core Data supports "nested" managed object contexts which allow for a flexible architecture that make it easy to support independent, cancellable, change sets. With a child context, you can allow the user to make a set of changes to managed objects that can then either be committed wholesale to the parent (and ultimately saved to the store) as a single transaction, or discarded. If all parts of the application simply retrieve the same context from, say, an application delegate, it makes this behavior difficult or impossible to support.

这种模式的原因是确保对托管对象图的更改受到适当的约束。Core Data 支持“嵌套”托管对象上下文,这允许灵活的架构,使支持独立的、可取消的、更改集变得容易。使用子上下文,您可以允许用户对托管对象进行一组更改,然后可以将这些更改作为单个事务批量提交给父级(并最终保存到商店),或者丢弃。如果应用程序的所有部分都只是从一个应用程序委托中检索相同的上下文,就会使这种行为难以或不可能支持。

Update:In iOS 10 Apple moved synchronisation from the sqlite file level up to the persistent coordinator. This means you can now create a private queue context and reuse the existing coordinator used by the main context without the same performance problems you would have had doing it that way before, cool!

更新:在 iOS 10 中,Apple 将同步从 sqlite 文件级别移到了持久协调器。这意味着您现在可以创建一个私有队列上下文并重用主上下文使用的现有协调器,而不会出现以前那样做的性能问题,很酷!

回答by hariszaman

By the way this documentof Apple is explaining this problem very clearly. Swift version of above for anyone interested

顺便说一下,Apple 的这份文档非常清楚地解释了这个问题。任何有兴趣的人都可以使用上面的 Swift 版本

let jsonArray = … //JSON data to be imported into Core Data
let moc = … //Our primary context on the main queue

let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = moc

privateMOC.performBlock {
    for jsonObject in jsonArray {
        let mo = … //Managed object that matches the incoming JSON structure
        //update MO with data from the dictionary
    }
    do {
        try privateMOC.save()
        moc.performBlockAndWait {
            do {
                try moc.save()
            } catch {
                fatalError("Failure to save context: \(error)")
            }
        }
    } catch {
        fatalError("Failure to save context: \(error)")
    }
}

And even simpler if you are using NSPersistentContainerfor iOS 10 and above

如果您在iOS 10 及更高版本中使用NSPersistentContainer那就更简单了

let jsonArray = …
let container = self.persistentContainer
container.performBackgroundTask() { (context) in
    for jsonObject in jsonArray {
        let mo = CarMO(context: context)
        mo.populateFromJSON(jsonObject)
    }
    do {
        try context.save()
    } catch {
        fatalError("Failure to save context: \(error)")
    }
}