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或限制并发没有。个人倾向于喜欢私人队列,因为我使用它的温暖的模糊和安全的感觉。

posted on 2017-05-16 13:47  鬼手渔翁  阅读(247)  评论(0)    收藏  举报

导航