原文地址:http://www.cocoanetics.com/2012/07/multi-context-coredata/

Multi-Context CoreData

When you start using CoreData for persisting your app data you start out with a single managed object context (MOC). This is how the templates in Xcode are set up if you put a checkmark next to “Use Core Data”.

Using CoreData in conjunction with NSFetchedResultsController greatly simplifies dealing with any sort of list of items which you would display in a table view. The

There are two scenarios where you would want to branch out, that is, use multiple managed object contexts: 1) to simplify adding/editing new items and 2) to avoid blocking the UI. In this post I want to review the ways to set up your contexts to get you what you want.

Note: I am wrapping my head around this myself for the very first time. Please notify me via e-mail about errors that I might have made or where I am explaining something incorrectly.

 

First, let’s review the single-context setup. You need a persistent store coordinator (PSC) to manage talking to the database file on disk. So that this PSC knows how the database is structured you need a model. This model is merged from all model definitions contained in the project and tells CoreData about this DB structure. The PSC is set on the MOC via a property. The first rule to remember: A MOC with a PSC will write to disk if you call its saveContext.

Consider this diagram. Whenever you insert, update or delete an entity in this single MOC then the fetched results controller will be notified of these changes and update its table view contents. This is independent of the saving of the context. You can save as rarely or as often as you want. Apple’s template saves on each addition of an entity and also (curiously) in applicationWillTerminate.

This approach works well for most basic cases, but as I mentioned above there are two problems with it. The first one is related to adding a new entity. You probably want to reuse the same view controller for adding and editing an entity. So you might want to create a new entity even before presenting the VC for it to be filled in. This would cause the update notifications to trigger an update on the fetched results controller, i.e. an empty row would appear shortly before the modal view controller is fully presented for adding or editing.

The second problem would be apparent if the updates accrued before the saveContext are too extensive and the save operation would take longer than 1/60th of a second. Because in this case the user interface would be blocked until the save is done and you’d have a noticeable jump for example while scrolling.

Both problems can be solved by using multiple MOCs.

The “Traditional” Multi-Context Approach

Think of each MOC as being a temporary scratchpad of changes. Before iOS 5 you would listen for changes in other MOCs and merge in the changes from the notification into your main MOC. A typical setup would look like this flow chart:

You would create a temporary MOC for use on a background queue. So allow the changes there to also be persisted you would set the same PSC on the temporary MOC as in the main MOC. Marcus Zarra put it like this:

Although the NSPersistentStoreCoordinator is not thread safe either, the NSManagedObjectContext knows how to lock it properly when in use. Therefore, we can attach as many NSManagedObjectContext objects to a single NSPersistentStoreCoordinator as we want without fear of collision.

Calling saveContext on the background MOC will write the changes into the store file and also trigger a  NSManagedObjectContextDidSaveNotification .

In code this would roughly look like this:

dispatch_async(_backgroundQueue, ^{
// create context for background
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
tmpContext.persistentStoreCoordinator = _persistentStoreCoordinator;
 
// something that takes long
 
NSError *error;
if (![tmpContext saveContext:&error])
{
// handle error
}
});

Creating a temporary MOC is very fast, so you don’t have to worry about frequently creating and releasing these temporary MOCs. The point is to set the persistentStoreCoordinator to the same one what we had on the mainMOC so that the writing can occur in the background, too.

I prefer this simplified setup of the CoreData stack:

- (void)_setupCoreDataStack
{ NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Database" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
  NSURL *storeURL = [NSURL fileURLWithPath:[[NSString cachesPath] stringByAppendingPathComponent:@"Database.db"]];
 
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_managedObjectModel];
 
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{ }
  _managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil];
}

Now please consider the notification handler which we set as target for whenever such a didSave notification arrives.

- (void)_mocDidSaveNotification:(NSNotification *)notification
{
NSManagedObjectContext *savedContext = [notification object];
  if (_managedObjectContext == savedContext)
{
return;
}
 
if (_managedObjectContext.persistentStoreCoordinator != savedContext.persistentStoreCoordinator)
{ return;
}
 
dispatch_sync(dispatch_get_main_queue(), ^{
[_managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
});
}

We want to avoid merging our own changes, hence the first if. Also if we have multiple CoreData DB in the same app we want to avoid trying to merge changes that are meant for another DB. I had this problem in one of my apps which is why I check the PSC. Finally we merge the changes via the provided mergeChangesFromContextDidSaveNotification: method. The notification has a dictionary of all the changes in its payload and this method knows how to integrate them into the MOC.

Passing Managed Objects Between Contexts

It is strictly forbidden to pass a managed object that you have gotten from one MOC to another. There is a simple method to sort of “mirror” a managed object via its ObjectID. This identifier is thread-safe and you can always retrieve it from one instance of an NSManagedObject and then call objectWithID: on the MOC you want to pass it to. The second MOC will then retrieve its own copy of the managed objects to work with.

NSManagedObjectID *userID = user.objectID;
 
// make a temporary MOC
dispatch_async(_backgroundQueue, ^{
// create context for background
NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
tmpContext.persistentStoreCoordinator = _persistentStoreCoordinator;
 
// user for background
TwitterUser *localUser = (TwitterUser *)[tmpContext objectWithID:userID];
 
// background work
});

The described approach is fully backwards-compatible all the way down to the first iOS version that introduced CoreData, iOS 3. If you are able to require iOS 5 as deployment target for your app then there is a more modern approach which we shall inspect next.

Parent/Child Contexts

iOS 5 introduced the ability for MOCs to have a parentContext. Calling saveContext pushes the changes from the child context to the parent without the need for resorting to the trick involving merging the contents from a dictionary describing the changes. At the same time Apple added the ability for MOCs to have their own dedicated queue for performing changes synchronously or asynchronously.

The queue concurrency type to use is specified in the new initWithConcurrencyType initializer on NSManagedObjectContext. Note that in this diagram I added multiple child MOCs that all have the same main queue MOC as parent.

Whenever a child MOC saves the parent learns about these changes and this causes the fetched results controllers to be informed about these changes as well. This does not yet persist the data however, since the background MOCs don’t know about the PSC. To get the data to disk you need an additional saveContext: on the main queue MOC.

The first necessary change for this approach is to change the main MOC concurrency type to NSMainQueueConcurrencyType. In the above mentioned _setupCoreDataStack the init line changes like shown below and the merge notification is no longer necessary.

_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:_persistentStoreCoordinator]

A lenghty background operation would look like this:

NSMangedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
NSMangedObjectContext *temporaryContext.parentContext = mainMOC;
 
[temporaryContext performBlock:^{   NSError *error;
if (![temporaryContext save:&error])
{ }
  [mainMOC performBlock:^{
NSError *error;
if (![_temporaryContext save:&error])
{ }
}];
}];

Each MOC now needs to be used with performBlock: (async) or performBlockAndWait: (sync) to work with. This makes sure that the operations contained in the block are using the correct queue. In the above example the lengthy operation is performed on a background queue. Once this is done and the changes are pushed to the parent via saveContext then there is also an asynchronous performBlock for saving the mainMOC. This again is happening on the correct queue as enforced by performBlock.

Child MOCs don’t get updates from their parents automatically. You could reload them to get the updates but in most cases they are temporary anyway and thus we don’t need to bother. As long as the main queue MOC gets the changes so that fetched results controllers are updated and we get persistence on saving the main MOC.

The awesome simplification afforded by this approach is that you can create a temporary MOC (as child) for any view controller that has a Cancel and a Save button. If you pass a managed object for editing you transfer it (via objectID, see above) to the temp context. The user can update all elements of the managed object. If he presses Save then you save the temporary context. If he presses cancel you don’t have to do anything because the changes are discarded together with the temporary MOC.

Does your head spin by now? If not, then here’s the total apex of CoreData Multi-Context-ness.

Asynchronous Saving

CoreData guru Marcus Zarra has shown me the following approach which builds on the above Parent/Child method but adds an additional context exclusively for writing to disk. As alluded to earlier a lenghty write operation might block the main thread for a short time causing the UI to freeze. This smart approach uncouples the writing into its own private queue and keeps the UI smooth as button.

The setup for CoreData is also quite simple. We only need to move the persistentStoreCoordinator to our new private writer MOC and make the main MOC be a child of this.

// create writer MOC
_privateWriterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_privateWriterContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
 
// create main thread MOC
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.parentContext = _privateWriterContext;

We now have to do 3 saves for every update: temporary MOC, main UI MOC and for writing it to disk. But just as easy as before we can stack the performBlocks. The user interface stays unblocked during the lengthy database operation (e.g. import of lots of records) as well as when this is written do disk.

Conclusion

iOS 5 greatly simplified dealing with CoreData on background queues and to get changes flowing from child MOCs to their respective parents. If you still have to support iOS 3/4 then these are still out of reach for you. But if you are starting a new project that has iOS 5 as minimum requirement you can immediately design it around the Marcus Zarra Turbo Approach as outlined above.

【转】多线程Core Data的更多相关文章

  1. iOS Core data多线程并发访问的问题

    大家都知道Core data本身并不是一个并发安全的架构:不过针对多线程访问带来的问题,Apple给出了很多指导:同时很多第三方的开发者也贡献了很多解决方法.不过最近碰到的一个问题很奇怪,觉得有一定的 ...

  2. 正确使用Core Data多线程的3种方式

    在#Pragma Conference 2015会议上,Marcus Zarra,撰写过关于Core Data和Core Animation的书,叙述了三种在多线程环境下使用Core Data的方法并 ...

  3. iOS之Core Data及其线程安全

    一.简介 Core Data是iOS5之后才出现的一个框架,它提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成OC对 ...

  4. 我为什么用 SQLite 和 FMDB 而不用 Core Data

    凭良心讲,我不能告诉你不去使用Core Data.它不错,而且也在变好,并且它被很多其他Cocoa开发者所理解,当有新人加入你的组或者需要别人接手你的项目的时候,这点很重要.更重要的是,不值得花时间和 ...

  5. [Cocoa]深入浅出 Cocoa 之 Core Data(1)- 框架详解

    Core data 是 Cocoa 中处理数据,绑定数据的关键特性,其重要性不言而喻,但也比较复杂.Core Data 相关的类比较多,初学者往往不太容易弄懂.计划用三个教程来讲解这一部分: 框架详解 ...

  6. iOS 数据持久化(3):Core Data

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css); @import url(/ ...

  7. 谈谈用SQLite和FMDB而不用Core Data

    谈谈用SQLite和FMDB而不用Core Data 发布于:2014-04-22 11:22阅读数:4235 凭良心讲,我不能告诉你不去使用Core Data.它不错,而且也在变好,并且它被很多其他 ...

  8. Core Data的使用(二)备

    一.基础概念深入 1.NSManagedObjectContext 被管理数据上下文就像便笺簿 当从数据持久层获取数据时,相当于把这些临时的数据拷贝写在便笺簿上,然后就可以随心所欲的修改这些值. 通过 ...

  9. 用 SQLite 和 FMDB 替代 Core Data

    本文转载至 http://blog.csdn.net/majiakun1/article/details/38680147 为什么我不使用Core Data Mike Ash 写到: 就个人而言,我不 ...

随机推荐

  1. ContextMenustrip 控件

    ContextMenustrip 控件是由 System.Windows.Forms.ContextMenustrip类提供,也是ToolstripMenu对象的容器,用来创建窗体的右键显示的菜单,主 ...

  2. c#配置文件app.config 与 Settings.settings

    本篇博客将介绍C#中Settings的使用.参考:https://docs.microsoft.com/zh-cn/visualstudio/ide/managing-application-sett ...

  3. synchronized同步锁

    在多线程的情况下,由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问.由于 ...

  4. <深入理解JavaScript>学习笔记(2)_揭秘命名函数表达式

    写在前面的话 注:本文是拜读了 深入理解JavaScript 之后深有感悟,故做次笔记方便之后查看. 感觉这章的内容有点深奥....略难懂啊. 先坐下笔记,加深一下印象吧. 我主要记一下自己感觉有用的 ...

  5. [模板]选择排序&&冒泡排序&&插入排序

    #include<iostream> #include<cstdio> #include<bits/stdc++.h> using namespace std; v ...

  6. SpringBoot MockMvc的单元测试

    对于类的测试,可以有很多的方式进行实现,比如可以使用PostMan,使用HttpClient请求等,这里主要讲的是MockMcv的测试 一:引入依赖 <dependency> <gr ...

  7. tensorflow(一):图片处理

    一.图片处理 1.图片存取 tf.gfile import tensorflow as tf import matplotlib.pyplot as plt image_bytes = tf.gfil ...

  8. bnu 4067 美丽的花环

    http://www.bnuoj.com/bnuoj/problem_show.php?pid=4067 美丽的花环 Time Limit: 1000ms Case Time Limit: 1000m ...

  9. MySQL之单表查询练习

    一.emp表 二.练习 1. 查询出部门编号为30的所有员工2. 所有销售员的姓名.编号和部门编号.3. 找出奖金高于工资的员工.4. 找出奖金高于工资60%的员工.5. 找出部门编号为10中所有经理 ...

  10. js点击事件在苹果端失效的问题

    在安卓机上,我们随意定义点击事件也能够在找到点击的元素,但是在苹果端上就是不行,怎么点击都没有效果.这是因为在苹果机上window禁止了手指误点功能,必须解除这一功能,或者给点击事件指引某个元素上绑定 ...