简体   繁体   English

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

[英]Core Data background context best practice

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 Car object, meaning: 我从我的服务器获取汽车信息JSON列表,然后我想将它与我的核心数据Car对象同步,这意味着:
If its a new car -> create a new Core Data Car object from the new info. 如果它是一辆新车 - >从新信息创建一个新的Core Data Car对象。
If the car already exists -> update the Core Data Car object. 如果汽车已经存在 - >更新Core Data 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 ? 我使用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例子,而是他们做了这样的事情:

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中的核心数据配置:

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

This is an extremely confusing topic for people approaching Core Data for the first time. 对于第一次接近Core Data的人来说,这是一个非常令人困惑的话题。 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). 我不是轻描淡写地说,但凭借经验,我有信心说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. 因此,最好将主线程上运行的主上下文的任何子进程限制为处理(编辑)已在主线程上的UI中呈现的数据。 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. 如果您将它用于一般同步任务,您可能希望处理的数据远远超出您当前在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. 即使您使用NSPrivateQueueConcurrencyType,对于子编辑上下文,您可能会在主上下文中拖动大量数据,这可能会导致性能下降和阻塞。 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. 不是最简单的模式。

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文档没有说清楚的是,您最有可能需要混合使用描述“旧”线程限制处理方式的页面上描述的技术,以及新的Parent-Child上下文处理方式。

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. 您最好的选择可能是(我在这里提供通用解决方案,最佳解决方案可能取决于您的详细要求),将NSPrivateQueueConcurrencyType保存上下文作为最顶层的父级,直接保存到数据存储区。 [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. 一个你用于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). mainsync (私有队列类型)和编辑 (私有队列类型)。 I've shown this arrangement as option B on the diagram. 我已将此安排显示为图表中的选项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:). 与同步上下文类似,您需要[编辑:配置主上下文以接收通知]保存数据时(或者如果您需要更多粒度,更新数据时)并采取措施合并数据(通常使用mergeChangesFromContextDidSaveNotification: )。 Note that with this arrangement, there is no need for the main context to ever call the save: method. 请注意,通过这种安排,主要上下文不需要调用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. 要理解父/子关系,请选择选项A:父子方法只是意味着如果编辑上下文获取NSManagedObjects,它们将首先“复制到”(注册)保存上下文,然后是主上下文,然后最后编辑上下文。 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 . 您可以对它们进行更改,然后在调用save时:在编辑上下文中,更改将保存到主上下文中 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. 您必须在主上下文中调用save:然后在保存上下文之前调用save:才能将它们写入磁盘。

When you save from a child, up to a parent, the various NSManagedObject change and save notifications are fired. 从子级保存到父级时,会触发各种NSManagedObject更改和保存通知。 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. 因此,例如,如果您使用获取结果控制器来管理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. 一些结果:如果在编辑上下文中获取对象和NSManagedObject A,则修改它并保存,以便将修改返回到主上下文。 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. 如果您尝试对存储在编辑上下文中的对象进行进一步修改,则您的修改将与主上下文中的对象不同步,并且任何保存编辑上下文的尝试都将引发错误。

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 (eg [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 any edits 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 thread as 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选项会导致它们都被编辑。在这种情况下配置你的应用程序就像选项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. BTW对你选择哪个选项不要太担心。 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. 从A开始并且如果您需要更改为B可能是一个好主意。这比您想象的更容易做出这样的改变,并且通常比您预期的后果更少。

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]. 苹果在[^ 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. 现在已经不在了,你没有因为常见错误而堕落,你可以专注于如何正确地做背景核心数据。

Create a new persistent store coordinator (no longer needed on iOS 10 see update below) and a private queue context. 创建一个新的持久性存储协调器(iOS 10上不再需要,请参阅下面的更新)和一个私有队列上下文。 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上,上下文具有自动执行此操作的属性)

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.html As 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-08-19,他们添加了“新示例代码,演示如何使用第二个Core Data堆栈来获取后台队列上的数据。”

Here is that bit from AAPLCoreDataStackManager.m: 这是来自AAPLCoreDataStackManager.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. 大多数使用Core Data的应用程序都使用单个持久性存储协调器来调解对给定持久性存储的访问。 Earthquakes shows how to use an additional "private" persistent store coordinator when creating managed objects using data retrieved from a remote server. 地震显示了在使用从远程服务器检索的数据创建托管对象时如何使用其他“私有”持久性存储协调器。

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). 主持久性存储协调器由单个“堆栈控制器”对象(CoreDataStackManager的实例)提供。 It is the responsibility of its clients to create a managed object context to work with the coordinator[^1]. 客户有责任创建托管对象上下文以与协调器[^ 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. 客户端可以使用后面的这些属性来设置其他持久性存储协调器,以与主协调器并行工作。

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. 主视图控制器是QuakesViewController的一个实例,它使用堆栈控制器的持久存储协调器从持久存储中获取地震以显示在表视图中。 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. 它创建绑定到专用队列的托管对象上下文,以从存储中获取数据并将更改提交到存储。

[^1]: This supports the "pass the baton" approach whereby—particularly in iOS applications—a context is passed from one view controller to another. [^ 1]:这支持“传递接力棒”方法,特别是在iOS应用程序中,上下文从一个视图控制器传递到另一个视图控制器。 The root view controller is responsible for creating the initial context, and passing it to child view controllers as/when necessary. 根视图控制器负责创建初始上下文,并在必要时将其传递给子视图控制器。

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. Core Data支持“嵌套”托管对象上下文,允许灵活的体系结构,以便轻松支持独立,可取消的更改集。 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. 如果应用程序的所有部分只是从应用程序委托中检索相同的上下文,则会使此行为难以或无法支持。

Update: In iOS 10 Apple moved synchronisation from the sqlite file level up to the persistent coordinator. 更新:在iOS 10中,Apple将同步从sqlite文件级别移至持久协调器。 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! 这意味着您现在可以创建一个私有队列上下文并重用主上下文使用的现有协调器,而不会出现以前那样的性能问题,很酷!

By the way this document of Apple is explaining this problem very clearly. 顺便说一句,Apple的这份文件非常清楚地解释了这个问题。 Swift version of above for anyone interested 上面的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 NSPersistentContainer for 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)")
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM