【CoreData】parent-child关系ManagedObjectContext应用

时间:2023-01-29 15:06:12

当我们一开始使用CoreData框架和唯一的MOC进行应用的数据持久化的时候,如果创建项目的时候选择了“使用CoreData”,这会是XCode自动生成的模板代码的样子。

【CoreData】parent-child关系ManagedObjectContext应用

同时,配合NSFetchedResultsController使用CoreData框架极大地简化了在各种各样的情况下使用tableview的麻烦。

有两种情形,我们想要CoreData多线程,或者使用多个MOC(ManagedObjectContext):  iOS5新提出了parent-child MOC大大简化了多线程使用CoreData的工作所以在这片文章就是讲使用新的父子MOC方法。

  1. 简化增加、编辑新的数据对象时的工作量。
  2. 避免阻塞用于UI更新的主线程。

iOS5新提出了parent-child MOC大大简化了多线程使用CoreData的工作所以在这片文章就是讲使用新的父子MOC方法。


首先来复习一下单一MOC的设立

你需要一个PSC来管理和磁盘上数据库的交流。所以为了让PSC了解数据库是如何构造的,你需要一个数据模型。这个数据模型是由所有项目中的模型综合监理起来的。 之后,PSC设定在MOC的一个属性上。引出MOC第一定律:使用[MOC saveContext]会让带有PSC属性的MOC执行将修改写入磁盘的操作。

【CoreData】parent-child关系ManagedObjectContext应用

和这个图表一样。不论什么时候使用一个MOC进行增删改查,NSFetchedResultsController会被告知数据已经更新,并且通知TableViews来更新内容。这和MOC的保存方法相互独立。你可以想什么时候保存就什么时候保存,苹果的模板是在applicationWillTerminate的时候进行保存。

这个方法在大多数情况下好用,但是就像之前所说的两种情形,第一种与增改新的实体有关。你可能想要在增改实体的时候重用同一个viewcontroller。所以可能甚至会在view展现之前就插入了一个新的实体。但是这回导致NSFetchedResultsController通知tableview更新数据,所以可能的情况是在view展现之前会有一个空的单元格短暂出现。

第二种情况会当更新发生在savecontext之前而且数据保存会使用超过1/60秒的时间。在这种情况下用户界面会停止响应,直到保存结束,你会发现在页面滚动的时候发生了可见的页面跳动。

两种情况都可以用多重MOC解决。


“传统”的多MOC方案

把每一个MOC想象成做出改动的草稿,在iOS5之前,使用CoreData需要监听其他MOC做出的改变,并且与之融合进主MOC当中。一个典型的设置会是这样的:

【CoreData】parent-child关系ManagedObjectContext应用

当在非主线程上创建临时MOC的时候,为了让数据能成功持久化,你需要将主MOC的PSC同样设置在临时MOC上,Marcus Zarra是这样解释的:

尽管PSC不是线程安全的,但是MOC知道怎样正确的对之使用读写锁。所以,想搞几个MOC都行,只要设置同一个PSC来避免冲突。

在非主线程使用saveContext会使数据写到磁盘上,而且也会触发NSManagedObjectContextDidSaveNotification。写成代码大概长这样。

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_async(_backgroundQueue,^{
   // create context for background
   NSManagedObjectContext *tmpContext=[[NSManagedObjectContext alloc]init];
   tmpContext.persistentStoreCoordinator=_persistentStoreCoordinator;
 
   // something that takes long
 
   NSError *error;
   if(![tmpContext save:&error])
   {
      // handle error
   }
});

创建一个临时MOC是嗖嗖的,所以不用担心频繁创建和销毁这些临时MOC的损失。这里的重点是所有临时MOC都设置和主MOC同样的PSC,这样就算在非主线程也能写入数据。

这里使用简化的方法来设立一个CoreData的存储。

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-(void)_setupCoreDataStack
{
   // setup managed object model
   NSURL *modelURL=[[NSBundle mainBundle]URLForResource:@"Database"withExtension:@"momd"];
   _managedObjectModel=[[NSManagedObjectModel alloc]initWithContentsOfURL:modelURL];
 
   // setup persistent store coordinator
   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])
   {
       // handle error
   }
 
   // create MOC
   _managedObjectContext=[[NSManagedObjectContext alloc]init];
   [_managedObjectContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
 
   // subscribe to change notifications
   [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(_mocDidSaveNotification:)name:NSManagedObjectContextDidSaveNotification object:nil];
}

现在考虑一下设置当触发didsave时的提醒回调函数。

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-(void)_mocDidSaveNotification:(NSNotification *)notification
{
   NSManagedObjectContext *savedContext=[notification object];
 
   // ignore change notifications for the main MOC
   if(_managedObjectContext==savedContext)
   {
      return;
   }
 
   if(_managedObjectContext.persistentStoreCoordinator!=savedContext.persistentStoreCoordinator)
   {
      // that's another database
      return;
   }
 
   dispatch_sync(dispatch_get_main_queue(),^{
      [_managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
   });
}

一开始要避免融合主MOC自己的改动,之后如果在一个APP有多个数据库,也要避免保存到拎一个数据库去。最后使用mergeChangesFromContextDidSaveNotification  方法融合改动。Notification有一个字典保存了所有的改动,而且这个方法知道怎么做将其融合到MOC之中。


在不同的MOC之中传递实体对象(MO)

直接传递在两个不同的MOC当中得到的MO是绝对禁止的。但是有个简单的方法“映射”一个MO:通过它的ObjectID。这个ID是线程安全的,所以总是可以用它来获得MO,方法是objectWithID:, 会返回一个复制了的MO。

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
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=[tmpContext objectWithID:userID];
 
   // background work
});

父子MOC

iOS5 使得MOC具有了一个属性:爹爹MOC。使用saveContext会将改变从儿子MOC直接推送到爹爹MOC去,而且不需要多余的指令。与此同时苹果赐予了MOC神奇地使用自己的同步、异步队列执行数据修改的能力。

队列的并行类型是在MOC的initWithConcurrencyType方法中初始化设置的。在下面这个图标当中我给一个爹爹MOC加了俩儿子MOC。

【CoreData】parent-child关系ManagedObjectContext应用

不论何时儿子MOC保存了一些改动,爹爹MOC都会知道,并且通知NSFetchResultsController对这些改变作出响应。但是这是数据还没写到磁盘上,因为儿子MOC(同时可能不是在主线程创造出来的MOC)并不知道PSC在哪,所以要持久写数据需要使用主MOC 在主队列上的saveContext方法。

第一个需要注意的地方是主MOC的类型是NSMainQueueConcurrencyType。在之前写的简单设立coreData存储的方法中,需要改变成这样。

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

一个多线程的数据操作会长这样:

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NSMangedObjectContext *temporaryContext=[[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext=mainMOC;
 
[temporaryContext performBlock:^{
   // do something that takes some time asynchronously using the temp context
 
   // push to parent
   NSError *error;
   if(![temporaryContext save:&error])
   {
      // handle error
   }
 
   // save parent to disk asynchronously
   [mainMOC performBlock:^{
      NSError *error;
      if(![mainMOC save:&error])
      {
         // handle error
      }
   }];
}];

每一个MOC 在进行数据操作的时候需要使用[MOC performBlock:(异步) /performBlockAndWait(同步)的方法。为啥呢,是因为每个MOC所在的线程或者队列是特定的,如果在非MOC创建的线程使用MOC进行数据处理是会出BUG的,因此使用这个方法,会自动迫使让你在正确的线程或者队列当中进行数据的操作。

注意儿子MOC是不会自动注意到爹爹的MOC的变化的,可以reload儿子MOC来同步这些改动,但是最多的情况下这些都不是事。只要主MOC得到了改动,那么fetchedcontroller会自动跟新,数据也会自动存储在磁盘上。

这个新的特性带来的最大的好处在于,你可以为任何一个带有保存或者取消功能的VC创建一个临时儿子MOC,如果把一个MO传递给这个MOC,用户就可以改动MO,如果他选择保存,就可以缓存所有的改动,如果他选择取消,那你啥也不用做因为所有的改动都会随着临时MOC被抛弃。

OK,脑子晕了吗?如果没有,现在有一整套多MOC的coredata实践


异步保存

Coredata 大神 Marcus Zarra 告诉我以下基于儿子/爹MOC的方法,增加了一个单独的写入用MOC。作为之前写方法的更新,那个方法还是有可能阻塞主线程。这个聪明的方法将写入保留在自己的非主队列当中,从而让界面继续如丝般顺滑。

【CoreData】parent-child关系ManagedObjectContext应用

这个方法还是蛮简单的,只要把PSC设置到我们新的私有写入MOC上,并且把主MOC设置成他的儿子MOC。

这样每做一次改变需要三次修改:临时MOC,主UIMOC 还有写入MOC。但是就像以前一样容易的是我们可以堆叠performBlocks. 用户界面永远不会因为长时间的写入而堵塞。


结论

iOS5 很牛地简化了非主线程队列使用CoreData的方法,使用父子MOC很容易就能实现并发存储。如果你还想支持ios3/4还是么问题的,但是如果iOS5是你支持的最低版本,那么就好办多了。

【CoreData】parent-child关系ManagedObjectContext应用的更多相关文章

  1. [NHibernate]Parent/Child

    系列文章 [Nhibernate]体系结构 [NHibernate]ISessionFactory配置 [NHibernate]持久化类(Persistent Classes) [NHibernate ...

  2. 服务启动Apache服务,错误Parent: child process exited with status 3 -- Aborting.解决

    不能启动apache,或者使用wamp等集成包后,唯独apache服务启动后有停止,但是把东西搬到其他机器上却没事问题可能和网络有关,我查了很多资料首先找打apache的错误报告日志,发现现实诸多的调 ...

  3. XPath学习:parent,child

    XPath 是一门在 XML 文档中查找信息的语言.XPath 可用来在 XML 文档中对元素和属性进行遍历. XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointe ...

  4. csharp: DataRelation objects to represent a parent/child/Level relationship

    /// <summary> /// /// </summary> /// <param name="sender"></param> ...

  5. &lbrack;NHibernate&rsqb;关联映射

    系列文章 [Nhibernate]体系结构 [NHibernate]ISessionFactory配置 [NHibernate]持久化类(Persistent Classes) [NHibernate ...

  6. Unity3d使用经验总结 数据驱动篇

    我这里说的数据驱动,不是指某种框架,某种结构,或者某种编码方式. 我要说的,是一种开发方式. 大家都知道,U3D中,我们可以为某个对象编写一个脚本,然后将这个脚本挂在对象上,那这个对象就拥有了相应的能 ...

  7. spring beans源码解读之--Bean的定义及包装

    bean的定义,包装是java bean的基础.再怎么强调它的重要性都不为过,因此深入 了解这块的代码对以后的代码研究可以起到事半功倍的功效. 1. Bean的定义BeanDefinition 1.1 ...

  8. NHibernate讲解

    第1章 NHibernate体系结构 总览 对NHibernate体系结构的非常高层的概览: 这幅图展示了NHibernate使用数据库和配置文件数据来为应用程序提供持久化服务(和持久化的对象). 我 ...

  9. QT父子与QT对象delete

    原地址:http://www.qteverywhere.com/archives/437 很多C/C++初学者常犯的一个错误就是,使用malloc.new分配了一块内存却忘记释放,导致内存泄漏.Qt的 ...

随机推荐

  1. &lbrack;虾扯蛋&rsqb; android界面框架-Window

    从纯sdk及framwork的角度看,android中界面框架相关的类型有:Window,WindowManager,View等.下面就以这几个类为出发点来概览下安卓开发的"界面架构&quo ...

  2. css单位盘点

    css单位有:px,em,rem,vh,vw,vmin,vmax,ex,ch 等等 1.px单位最常见,也最直接,这里不做介绍. 2.em:em的值并不是固定,它继承父级元素的字体大小,所以层数越深字 ...

  3. ehcache报错

    jfinal2.0+tomcat7+ehcache2.6.11+Linux Linux version 2.6.18-164.el5 (mockbuild@x86-002.build.bos.redh ...

  4. lecture4-神经网络在语言上的应用

    Hinton第四课 这一课主要介绍神经网络在语言处理上应用,而主要是在文本上,并附上了2003年Bengio 等人的19页的论文<A Neural Probabilistic Language ...

  5. android wifi总结

    大致可以分为四个主要的类ScanResult wifiConfiguration WifiInfo WifiManager (1)ScanResult,主要是通过wifi 硬件的扫描来获取一些周边的w ...

  6. php discuz框架接口不能正常访问的问题

    本人php小白,无php编程基础,直接上php服务器部署,后果很严重.....所以务必看完请给”顶“给评论,以表示对小白的鼓励和赞赏! 关于discuz框架,独自加班,废寝忘食,然已无力吐槽..... ...

  7. &lbrack;转&rsqb;常用的130个vim命令

    原帖地址:http://www.oschina.net/news/43167/130-essential-vim-commands 从 1970 年开始,vi 和 vim 就成为了程序员最喜爱的文本编 ...

  8. JS 优化条件语句的5个技巧

    前言 在使用 JavaScript 的时候,有时我们会处理大量条件语句,这里有5个技巧帮助我们编写更简洁的条件语句. 一.对多个条件使用 Array.includes 例子: function con ...

  9. hbuilder下用plus&period;barcode&period;Barcode做二维码扫描,当二维码容器的高度设置过低时,启动扫描会发生闪退

    解决办法: 将固定高度改为百分比

  10. Maven常用的几个命令

    mvn clean.mvn package:如果本地仓库中没有需要的jar,第一次执行命令的时候会从仓库下jar包 *) mvn clean :clean项目 *) mvn compile :  编译 ...