(转)Core Data2

保存改变
这时候,可是我们还是没有接触到持久化存储协调器或持久化存储。新的模型对象—rootItem,仅仅在内存中。如果我们想要保存模型对象的状态(在这种情况下只是一个对象),我们需要保存context:

?
1
2
3
NSError *error = nil ;
  
if (! [managedObjectContext save:&error]) {   // Uh, oh. An error happened. :( )}


这个时候,很多事情将要发生。首先发生的是managed object context计算出改变的内容。这是context的职责,追踪出任何你在context管理对象中做出的改变。在我们的例子中,我们到现在做出的唯一改变就是插入一个对象,即我们的rootItem。

然后Managed object context将这些改变传给持久化存储协调器,让它将这些改变传播给store。持久化存储协调器会协调store来将我们插入的对象写入到磁盘上的SQL数据库。NSPersistentStore类管理着和SQLite的实际交互,并且产生需要被执行的SQL代码。持久化存储协调器的规则就是简单的调整store和context之间的交互。在我们的例子中,这个规则相当简单,但是,复杂的设置可以有多个stores和多个contexts。

更新关系
Core Data的能力在于管理关系。让我们着眼于简单的情况—增加我们第二个item,并且使它成为rootItem的子item:
?
1
Item *item = [IteminsertNewObjectInManagedObjectContext:managedObjectContext]; item.parent =rootItem; item.title = @ "foo" ;


好了。这些改变再次的仅仅存在于managed object context中。一旦我们保存了context, managed object context将会告诉持久化存储协调器,像增加第一个对象一样增加新创建的对象到数据库文件中。但这也将会更新从第二个item到第一个item之间的关系,或从第一个到第二个item的关系。记住Item实体是如何有一个父子关系的。同时他们之间有相反的关系。因为我们设置第一个item为第二个item的父类时,第二个item将会变成第一个item的子类。managed object context追踪这些关系,持久化存储协调器和store保存这些关系到磁盘。

弄清对象
我们已经使用我们程序一会儿了,并且已经为根item增加了一些sub-items,甚至sub-items到sub-items。然后,我们再次启动我们的程序。Core Data已经将这些items之间的关系保存到了数据库文件。对象图形是持久化的。我们现在需要取出根item,所以我们可以显示底层items的列表。有两种方法。我们先看简单点的方法。

当根Item对象创建并保存之后我们可以向它请求它的NSManagedObjectID,这是一个不透明的对象,可以唯一代表该根Item。我们可以保存这个对象到NSUSerDefaults,像这样:

?
1
2
NSUserDefaults *defaults = [ NSUserDefaultsstandardUserDefaults ];                
[defaultssetURL:rootItem.managedObjectID.URIRepresentationforKey:@ "rootItem" ];


现在,当程序重新运行时,我们可以像这样返回得到这个对象:

?
1
2
3
4
5
NSUserDefaults *defaults = [ NSUserDefaults standardUserDefaults];
NSURL *uri = [defaults URLForKey:@ "rootItem" ];
NSManagedObjectID *moid = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:uri];
NSError *error = nil ;
Item *rootItem = ( id ) [managedObjectContext existingObjectWithID:moid error:&error];

 

很明显,在一个真实的程序中,我们需要检查NSUserDefaults是否真正返回一个有效值。

managed object context要求持久化存储协调器从数据库取得指定的对象。根对象现在被恢复到context中。然而,其他所有的items仍然不在内存中。

rootItem有一个子关系。但那儿还没有什么。我们想要显示rootItem的子item,因此我们需要调用:

?
1
NSOrderedSet *children = rootItem.children;


现在发生的是,context标注这个根item的子关系为默认。Core Data已经标注这个关系为仍需要被解决。既然我们已经在这个时候访问了它,context将会自动配合持久化存储协调器来将这些子 items载入到context中。

这听起来可能非常不重要,但是在这个时候真正发生了很多事情。如果任何子对象偶然发生在内存中,Core Data保证会复用那些对象。这是Core Data独一无二的功能。在context内,从不会存在第二个相同的单一对象来代表一个给定的item。

第二,持久化存储协调器有它自己内部对象值的缓存。如果context需要一个指定的对象(比如一个子item),并且持久化存储协调器在缓存中已经有需要的值,那么,对象可以不通过store而被直接加到context。这很重要,因为访问store就意味这执行SQL代码,这比使用内存中存在的值要慢很多。

随着我们越过item到子item到子item的子item,我们慢慢地把整个对象图形引用到了managed object context。而这些对象都在内存中之后,操作对象以及传递关系就会变得非常快,既然我们只是在managed object context里操作。我们跟本不需要访问持久化存储协调器。在我们的Item对象上访问title,父子属性是非常快而且高效的。

由于它会影响性能,所以了解数据在这些情况下怎么取出来是非常重要的。在我们特定的情况下,由于我们并没接触到太多的数据,所以这并不算什么,但是一旦你接触了,你将需要了解在背后发生了什么。

当你越过一个关系(比如在我们例子中的父关系或子关系)下面三种情况将有一种会发生:

(1)对象已经在context中,这种操作基本上是没有任何代价的。

(2)对象不在context中,但是因为你最近从store中取出过对象,所以持久化存储协调器缓存了对象的值,这个操作还算廉价(但是,一些操作会被锁住)。

操作耗费最昂贵的情况是(3),当context和持久化存储协调器都是第一次访问这个对象,这中情况必须通过store从SQLite数据库取回。第三种情况比(1)和(2)需要付出更多代价。

如果你知道你必须从store取回对象(你没有这些对象),当你限制一次取回多少个对象时,将会产生很大的不同。在我们的例子中,我们希望一次性取出所有child items,而不是一个接一个。我们可以通过一个特别的技巧NSFetchRequest,但是我们要注意,当我们需要做这个操作时,我们只需要执行一次取出请求,因为一次取出请求将会造成选项(3)发生;这将总是访问SQLite数据库。

因此,当需要显著提升性能时,检查对象是否已经存在将变得非常有意义。你可以使用-[NSManagedObjectContext objectRegisteredForID:]来检测一个对象是否已经存在。

改变对象的值
现在,我们可以说,我们已经改变我们一个Item对象的title:

?
1
item.title = @ "New title" ;  


当我们这样做时,items的title改变了,此外,managed object context会标注这个item已经被改变,这样当我们在context中调用-save:时,这个对象将会通过持久化存储协调器和附属的store保存起来。context最关键的职责之一就是跟踪任何改变。

从最后一次保存开始,context知道哪些对象被插入,改变,删除。你可以通过-insertedObjects, -updatedObjects, and –deletedObjects方法来执行这些操作。同样的,你可以通过-changedValues方法来询问一个被管理的对象哪些值被改变了。这个方法正是Core Data能够将你做出的改变推入到数据库的原因。

当我们插入一个新的Item对象时,Core Data知道需要将这些改变存入store。那么,将你改变对象的title时,也会发生同样的事情。

保存values需要协调持久化存储协调器和持久化store依次访问SQLite数据库。当和在内存中操作对象比起来,取出对象和值,访问store和数据库是非常耗费资源的。不管你保存了多少更改,一次保存的代价是固定的。并且每个变化都有成本,这仅仅是SQLite的工作。当你做很多更改的时候,需要将更改打包,并批量更改。如果你保存每一次更改,将要付出很高的代价,因为你需要经常做保存操作。如果你很少做保存,那么你将会有一大批更改交给SQLite处理。

同样需要注意到,保存操作是原子性的。他们都是事务。要么所有的更改会被提交给store/SQLite数据库,要么任何更改都不被保存。当实现自定义NSIncrementalStore基类时,这一点一定要牢记在心。要么确保保存永远不会失败(比如说不会发生冲突),要么当保存失败时,你store的基类需要恢复所有的改变。否则,在内存中的对象图形最终和保存在store中的对象不一致。

如果你使用一个简单的设置,保存操作通常不会失败。但是Core Data允许每个持久化存储协调器有多个context,所以你可能陷入持久化存储协调器层级的错误。改变是对于每个context的,另一个context的更改可能导致冲突。Core Data甚至允许完全不同的堆栈访问磁盘上相同的SQLite数据库。这明显也会导致冲突(比如,一个context想要更新一个对象的值,而另一个context想要删除这个对象)。另一个导致保存失败的原因可能是验证。Core Data支持对象复杂的验证政策。这个问题比较高级。一个简单的验证规则可能是:Item的标题不能超过300个字符。但是Core Data也支持通过属性进行复杂的验证政策。

结束语
 如果CoreData看起来让人害怕,这最有可能是因为它的灵活性允许你可以通过非常复杂的方法使用它。始终记住:尽量保持简单,它会让开发变得更容易,并且把你和你的用户从麻烦中拯救出来。除非你确信它会带来帮助,才去使用更复杂的东西,比如说是background contex。去使用一个简单的CoreData堆栈,并且使用我们在这篇文章中讲到的知识吧,你将很快会真正体会到Core Data能为你做什么,并且学到它是怎么缩短你开发周期的。

你可能感兴趣的:((转)Core Data2)