Multi-Context CoreData
多上下文CoreData
转自 https://www.cocoanetics.com/2012/07/multi-context-coredata/
当您开始使用CoreData持久化应用程序数据时,您可以使用单个托管对象上下文(MOC)开始。这是Xcode中的模板如何在“使用核心数据”旁边添加一个复选标记。
将CoreData与NSFetchedResultsController结合使用可大大简化处理您在表视图中显示的任何项目列表。
有两种情况需要分支,即使用多个托管对象上下文:1)简化添加/编辑新项目,2)避免阻止UI。在这篇文章中,我想回顾一下设置你的上下文以获得你想要的内容的方法。
注意:我第一次把自己包围在我身边。请通过电子邮件通知我关于我可能做出的错误或我正在解释的错误。
首先,我们来看一下单一上下文设置。您需要一个永久存储协调器(PSC)来管理与磁盘上的数据库文件的通话。所以这个PSC知道数据库的结构,你需要一个模型。该模型从项目中包含的所有模型定义合并,并告知CoreData关于此DB结构。PSC通过财产设置在MOC上。要记住的第一条规则:如果您调用其saveContext,具有PSC的MOC将写入磁盘。
考虑这个图。每当您在此单个MOC中插入,更新或删除实体时,获取的结果控制器将被通知这些更改并更新其表视图内容。这与上下文的保存无关。您可以很少或经常保存您想要的。苹果的模板可以节省每个添加的实体,并且(在好奇中)在applicationWillTerminate中。
这种方法对于大多数基本情况都很好,但正如我上面提到的,它有两个问题。第一个是添加一个新的实体。您可能想要重复使用相同的视图控制器来添加和编辑实体。因此,您可能希望创建一个新的实体,甚至在呈现VC以便填写之前,这将导致更新通知触发获取的结果控制器上的更新,即,在视图控制器之前不久就会出现一个空行完全呈现为添加或编辑。
如果在saveContext之前累积的更新过多,并且保存操作将花费超过1/60秒的时间,则第二个问题将显而易见。因为在这种情况下,用户界面将被阻止,直到保存完成,并且您在滚动时会有明显的跳跃。
通过使用多个MOC可以解决这两个问题。
“传统”多语境方法
将每个MOC都视为暂时的更改的暂存器。在iOS 5之前,您将听取其他MOC的更改,并将通知中的更改合并到主MOC中。典型的设置如下图所示:
您将创建一个用于后台队列的临时MOC。因此,允许在那里进行的更改也将持续存在,您将在临时MOC中设置与主要MOC中相同的PSC。马库斯·扎拉(Marcus Zarra)表示:
虽然NSPersistentStoreCoordinator也不是线程安全的,但NSManagedObjectContext知道如何在使用时正确地锁定它。因此,我们可以将尽可能多的NSManagedObjectContext对象附加到单个NSPersistentStoreCoordinator,而不用担心碰撞。
在后台调用saveContext MOC会将更改写入存储文件,并触发 NSManagedObjectContextDidSaveNotification。
在代码中,大致如下所示:
dispatch_async ( _backgroundQueue,^ { //为后台创建上下文 NSManagedObjectContext * tmpContext = [ [ NSManagedObjectContext alloc ] init ] ; tmpContext.persistentStoreCoordinator = _persistentStoreCoordinator; //需要很长时间 NSError *错误; if (![ tmpContext save:& error ] ) { // handle error } } ) ; |
创建一个临时MOC是非常快的,所以你不必担心经常创建和发布这些临时MOC。关键是将persistentStoreCoordinator设置为与mainMOC相同的一个,以便写入也可以在后台进行。
我更喜欢CoreData堆栈的简化设置:
- (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 :&amp ; error ] ) { // handle error } //创建MOC _managedObjectContext = [ [ NSManagedObjectContext alloc ] init ] ; [ _managedObjectContext setPersistentStoreCoordinator : _persistentStoreCoordinator ] ; //订阅更改通知 [ [ NSNotificationCenter defaultCenter ] addObserver : self selector :@selector ( _mocDidSaveNotification :) name : NSManagedObjectContextDidSaveNotification对象:nil ] ; } |
现在请考虑通知处理程序,我们设置为目标,只要这样的一个通知到达。
- (void ) _mocDidSaveNotification :(NSNotification * ) notification { NSManagedObjectContext * savedContext = [ notification object ] ; //忽略主要MOC的更改通知 if ( _managedObjectContext == savedContext ) { return ; } if ( _managedObjectContext.persistentStoreCoordinator != savedContext.persistentStoreCoordinator ) { //这是另一个数据库 返回 ; } dispatch_sync ( dispatch_get_main_queue (),^ { [ _managedObjectContext mergeChangesFromContextDidSaveNotification : notification ] ; } ) ; } |
我们想避免合并我们自己的变化,因此第一个如果。另外如果我们在同一个应用程序中有多个CoreData数据库,我们希望避免尝试合并另一个DB的更改。我在我的一个应用程序中出现了这个问题,这就是为什么我检查PSC。最后,我们通过提供的mergeChangesFromContextDidSaveNotification:方法合并更改。该通知具有其有效载荷中所有变化的字典,并且该方法知道如何将它们整合到MOC中。
在上下文之间传递管理对象
将您从一个MOC获得的管理对象传递给另一个管理对象是严格禁止的。有一个简单的方法来通过它的ObjectID对被管理对象进行“镜像”排序。该标识符是线程安全的,您可以随时从NSManagedObject的一个实例中检索它,然后调用objectWithID:在要传递给它的MOC上。然后,第二个MOC将检索自己的托管对象的副本。
NSManagedObjectID * userID = user.objectID; // make a temporary MOC dispatch_async ( _backgroundQueue,^ { //为后台创建上下文 NSManagedObjectContext * tmpContext = [ [ NSManagedObjectContext alloc ] init ] ; tmpContext.persistentStoreCoordinator = _persistentStoreCoordinator; //用户为背景 TwitterUser * localUser = [ tmpContext objectWithID : userID ] ; // background work } ) ; |
所描述的方法完全向后兼容到第一个引入CoreData,iOS 3的iOS版本。如果你能够要求iOS 5作为你的应用程序的部署目标,那么我们将会检查一个更现代的方法。
父/子语境
iOS 5引入了MOC拥有parentContext的能力。调用saveContext将更改从子上下文推送到父进程,而不需要诉诸涉及从描述更改的字典合并内容的技巧。同时,苹果公司还增加了MOCs拥有自己的专用队列来同步或异步执行更改的能力。
要使用的队列并发类型在NSManagedObjectContext上的新initWithConcurrencyType初始化程序中指定。请注意,在此图中,我添加了多个子项MOC,它们都具有与父项相同的主队列MOC。
每当一个孩子MOC保存父母了解这些变化,并且这导致获取的结果控制器也被通知这些变化。然而,这还没有保留数据,因为背景MOC不了解PSC。要获取数据到磁盘,您需要一个额外的saveContext:在主队列MOC上。
这种方法的第一个必要的改变是将主要的MOC并发类型更改为NSMainQueueConcurrencyType。在上面提到的_setupCoreDataStack中,init行的更改如下所示,并且不再需要合并通知。
_managedObjectContext = [ [ NSManagedObjectContext alloc ] initWithConcurrencyType : NSMainQueueConcurrencyType ] ; [ _managedObjectContext setPersistentStoreCoordinator : _persistentStoreCoordinator ] ; |
一个冗长的背景操作将如下所示:
NSMangedObjectContext * temporaryContext = [ [ NSManagedObjectContext alloc ] initWithConcurrencyType : NSPrivateQueueConcurrencyType ] ; temporaryContext.parentContext = mainMOC; [ temporaryContext performBlock :^ { //做某些使用temp context异步处理的事情 // push to parent NSError * error; if (![ temporaryContext save :&amp ; error ] ) { // handle error } //将父节点异步保存到磁盘 [ mainMOC performBlock :^ { NSError * error; if (![ mainMOC save :&amp ; error ] ) { // handle error } } ] ; } ] ; |
每个MOC现在需要与performBlock:(async)或performBlockAndWait:(sync)一起使用。这将确保块中包含的操作正在使用正确的队列。在上述示例中,在后台队列上执行冗长的操作。一旦完成,通过saveContext将更改推送到父进程,那么还有一个用于保存mainMOC的异步performBlock。这再次发生在由performBlock执行的正确队列上。
儿童MOC不会自动从父母处获得更新。您可以重新加载它们以获取更新,但在大多数情况下,它们是临时的,因此我们不需要麻烦。只要主队列MOC获得更改,以便获取的结果控制器被更新,并且我们在保存主MOC时持续存在。
这种方法提供的令人敬畏的简化是您可以为具有“取消”和“保存”按钮的任何视图控制器创建临时MOC(作为子代)。如果传递被管理对象进行编辑,则将它(通过objectID,见上文)传递给临时上下文。用户可以更新被管理对象的所有元素。如果他按保存,则保存临时上下文。如果他按下取消,你不必做任何事情,因为这些变化与临时MOC一起被丢弃。
你的头转动了吗?如果没有,那么这里是CoreData Multi-Context-ness的全部顶点。
异步保存
CoreData大师Marcus Zarra向我展示了以上的父/子方法的方法,但添加了专门用于写入磁盘的附加上下文。如前所述,一个冗长的写入操作可能会在短时间内阻止主线程,导致UI冻结。这种聪明的方法将写入分解成自己的私人队列,并保持UI平滑的按钮。
CoreData的设置也很简单。我们只需要将persistentStoreCoordinator移动到我们的新私人作家MOC,并使主要的MOC成为这个的小孩。
// create writer MOC _privateWriterContext = [ [ NSManagedObjectContext alloc ] initWithConcurrencyType : NSPrivateQueueConcurrencyType ] ; [ _privateWriterContext setPersistentStoreCoordinator : _persistentStoreCoordinator ] ; //创建主线程MOC _managedObjectContext = [ [ NSManagedObjectContext alloc ] initWithConcurrencyType : NSMainQueueConcurrencyType ] ; _managedObjectContext.parentContext = _privateWriterContext; |
我们现在必须为每个更新做3次保存:临时MOC,主UI MOC并将其写入磁盘。但是和以前一样简单,我们可以堆叠performBlocks。用户界面在冗长的数据库操作期间(例如导入大量记录)以及写入磁盘时,会保持未屏蔽。
结论
iOS 5大大简化了处理CoreData的后台队列,并将更改从儿童MOCs转移到各自的父母。如果您仍然需要支持iOS 3/4,那么这些仍然无法实现。但是,如果您正在开发一个以iOS 5为最低要求的新项目,您可以马上将其设计围绕上述Marcus Zarra Turbo方法进行设计。
Zach Waldowski指出,对于“编辑视图控制器”使用私有队列并发类型可能是过度的。如果您在子视图控制器上下文中使用NSContainmentConcurrencyType,则不需要使用performBlock。您仍然需要在mainMOC上执行锁定才能保存。
约束并发类型是上下文的“旧方法”,但这并不意味着它是遗产。它将上下文的操作简单地与自我管理的线程模型联系起来。为每个新的视图控制器旋转私人队列是浪费,不必要和缓慢的。-performBlock:和-performBlockAndWait:由于某种原因不适用于confinement并发类型,因为当您在“编辑”视图控制器设置中执行多个上下文时,不需要阻塞或锁定。
NSManagedObjectContext知道如何智能地保存和合并,并且因为主线程上下文绑定到主线程,所以它的合并总是安全执行的。编辑视图控制器就像主视图控制器一样绑定到主线程; 单独操作的唯一方法只是在UI界面,这就是为什么在这里使用限制并发类型是合适的。编辑环境在概念上不是一个“新”的事情,它只是推迟到后来的变化,同时还允许你完全丢弃这些变化。
所以它真的归结到你个人的喜好:私人队列与performBlock或限制并发没有。个人倾向于喜欢私人队列,因为我使用它的温暖的模糊和安全的感觉。





浙公网安备 33010602011771号