core data 系列一:概述

来源:互联网 发布:魔兽60年代魔兽数据库 编辑:程序博客网 时间:2024/06/06 05:22

翻译原文章:http://www.objc.io/issue-4/core-data-overview.html    

在2005年苹果公司推出了core data 框架。core data 是一个模型层的技术,帮助创建代表APP数据状态的模型层。core data也是一个持久化技术,能够将模型对象的状态存入磁盘中。但是,core data 框架不仅仅是一个加载和保存数据的框架,它同时处理在内存中的数据。

    core data 不仅仅是Object-relational mapping (O/RM), 也不仅仅是SQL框架,如果你仅仅想一个O/RM或者SQL框架,那core data 不太适合你。

   core data 的一个非常强大的地方是对象图形管理(object graph management),这是需要去了解和掌握的一个方面。

    core data 完全独立于 UI界面,是独立的model 层。

   Stack

   core data 有很多的组成部分,非常灵活。  当所有的组建组合在一起,一般称为core data stack。 主要有两部分对象图形管理和持久化(例如,保存和获取模型对象)。

    在这两部分之间(即处于stack中间的),是持久化存储协调器(PSC),可被认为是中间审查员。它将对象图形管理部分和持久化部分绑定在一起,当它们两者中的任何一部分需要和另一部分交流时,这便需要PSC来调节了。


对象图形管理是应用模型层逻辑存在的地方。模型层的对象存在于一个context内。在大多数的应用创建时,只有一个context,并且所有的对象存在于那个context中。Core Data支持许多contexts,但是每个context和其他context区分的都很清楚。需要注意的是,对象和相应的context是相关联的,每个被管理的对象都知道自己属于哪个context,并且每个context都知道自己管理着哪些对象。
       stack的另一部分就是持久化发生的地方,即Core Data从文件系统读取或写入的地方。持久化存存储协调器(persistent store coordinator)都有一个属于自己的持久化存储,并且这个store在文件系统中与SQLite数据库交互。为了支持更高级的设置,Core Data可以将多个存储附属于同一个持久化存储协调器,并且还有很多除了SQL存储类型可供选择的类型。  

    其实大部分情况,是如下的情况:


组件如何共同工作

让我们迅速的看一个说明组件如何协同工作的例子。在我们a full application using Core Data的文章中,我们正好有一个实体,即一种对象:我们有一个Item 实体有一个title属性。每一个item可以拥有子items,因此,我们有一个父子关系。

        这是我们的数据模型,正如我们在Data Models and Model Objects文章中提到的一样,一种特别的对象在CoreData中被称为实体(Entity)。在这个例子中,我们只有一个Item实体。同样的,我们有一个NSManagedObject子类叫做Item。这个Item实体映射到Item类上。在datamodels article中会详细的谈到这个。

我们的程序仅有一个根Item。这并没有什么神奇的。这是一个我们用来显示item层级的底层Item。这是一个我们永远不会为其设置父类的Item。

当程序运行时,我们按照上面描绘的设置我们的Stack,一个store,一个managed object context,一个persistent store coordinator来将他们相互关联。

在第一次运行时,我们并没有任何items。我们需要做的第一件事就是创建根item。你通过将他们插入context来增加管理对象。

创建对象

插入对象似乎很笨重,我们通过NSEntityDescription的如下方法来插入:

+ (id)insertNewObjectForEntityForName:(NSString *)entityName                inManagedObjectContext:(NSManagedObjectContext *)context

我们建议你增加两个简便的方法到你的模型类中:

+ (NSString *)entityName {    return @“Item”; }  + (instancetype)insertNewObjectInManagedObjectContext:(NSManagedObjectContext *)moc; {    return [NSEntityDescription insertNewObjectForEntityForName:[self entityName]                                          inManagedObjectContext:moc]; } 
现在,我们可以这样插入我们的根对象了:

Item *rootItem = [IteminsertNewObjectInManagedObjectContext:managedObjectContext];
现在,在我们的managedobject context(MOC)中有一个唯一的item。Context知道这个新插入进来需要被管理的对象,而被管理的对象rootItem也知道这个Context(因为它有一个-managedObjectContext方法)。

保存变化

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

NSError *error = nil;if (! [managedObjectContext save:&error]) {    // Uh, oh. An error happened. :(}
这里发生了很多事情!首先,managed object context会检查什么发生了改变,因为managed object context 的责任就是跟踪所管理的managed objects的任何变化。在我们的例子中,唯一的变化就是我们插入了一个rootItem跟对象。

Managed object context  把这些变化传递给 persistent store coordinator,让协调器协调store(这个例子中时SQL store)把插入的对象写入磁盘上的SQL数据库中。NSPersistentStore 负责真正的与SQLlite进行交互和在需要的时候产生SQL代码。persistent store coordinator’s 的任务只是简单的协调store 和 context 工作。我们的例子中相对简单,但是有很多store和很多context的情况下就非常复杂了。

更新关系

core data 的强大在于能管理关系。让我们添加第二个item并且使其称为rootItem的子item:

Item *item = [Item insertNewObjectInManagedObjectContext:managedObjectContext];item.parent = rootItem;item.title = @"foo";
再次,这些变化仅存在于在context中。和第一个对象一样,当我们保存context的时候,context会通知persistent store coordinator去添加一个新的对象到新数据库文件中。  但是,还有一个作用就是更新我们的第二个item和第一个item的关系,让他们保持父子关系。managed object context 也会跟踪这些关系,persistent store coordinator 会把这些关系保存到磁盘中去。

获取对象

假设我们已经使用我们的app一段时间并且已经添加了一些子tems和相应的子item,core data会保存item之间的关系到数据库文件中,对象图形也已经被保存。我们现在需要获得根item,这样我们能够一系列的item。我们有两种方法获得,现在先用相对简单的一种方法。

当我们创建rootItem并且保存后,我们能够通过这个对象的NSManagedObjectID获取到它。这是一个唯一代表rootItem隐藏的对象。我们能够存储这个对象,例如通过NSUSerDefaults,就像下面这样

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];[defaults setURL:rootItem.managedObjectID.URIRepresentation forKey:@"rootItem"];

当这个app运行的时候,我们能够通过如下方法获得rootItem

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];
其实在真正的app中,我们需要检查NSUserDefaults是否需要返回一个有效值。

实现过程是:managed object context 让 persistent store coordinator 去从数据库获得特定的对象。root 对象取回到了context,但是其他的item还没有回到内存中。rootItem有一个children关系,但是,现在还没有,我们想展示rootItem的子item,我们可以通过调用如下的方法:

NSOrderedSet *children = rootItem.children;
发生的事情是:context发现rootItem的children的关系是默认的,core data 标记这个关系需要解决。然后,context将自动和persistent store coordinator 进行协作把一些列子items取道context中。

这些可能显得非常繁琐,但是这里事实上有很多过程。如果子对象已经存在于内存中,core data 将重复使用这个item,这就是core data的唯一性。

第二:persistent store coordinator有它自己内部对象值的缓存。如果context需要一个特定的对象(例如一个子item),并且persistent store coordinator 在缓存中已经有相应的值,对象可以被直接加到context而不必通过store。这很重要,因为访问store就意味这执行SQL语句,这比使用内存中存在的值要慢很多。

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

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

当你获取一个关系的时候(例如父item和子item),三个情况中的一种将会发生:(1)对象已经存在context中,获取关系基本上没有代价,(2)对象不存在context,但是persistent store coordinator 有缓存,因为最近已经从store中获取过对象,相对比较没有代价。(3)如果没有存在context和persistent store coordinator都是第一次去取,需要从SQLite中获取,这是最耗费的事情。

如果你知道你必须从store提取对象(因为你还没有使用过她们),如果你能减少一次性获取对象的数量将很大提交效率。在我们的例子中,我们想一次性提取所有的子item而不是一个个地提取。可以通过特定的NSFetchRequest语句实现。但是我们必须小心使用fetch request,因为每次请求都会造成情况(3)的发生,将每次从SQLite数据库提取。所以当效率很重要时,检查对象是否已经存在时很有必要的。你能通过-[NSManagedObjectContext objectRegisteredForID:]去检测。

改变对象的值

现在,让我们改变一个item对象的title值。

Now, let’s say we are changing the title of one of our Item objects:

item.title = @"New title";

当我们执行这个代码的时候,item对象的title已经改变了。但是传统意义上说,managed object context标记了这个item已经改变了,这样当context执行-save:方法的时候context会通过persistent store coordinator把这个变化写入store中去。context的重要责任就时记录这些变化。

context知道自从上次保存后的哪些对象被插入,改变和删除。你可以通过 -insertedObjects-updatedObjects, 和 -deletedObjects去执行对对象的前述操作。同样的,你能够调用context的-changedValues方法去了解哪些值已经被改变了。虽然你可能一般情况下不需要知道这个,但是这是core data的依据去把这些改变存入数据库中。

当你插入一些新的item,core data 知道必须把这个对象存入store中去,同样如果你改变title,同样的事情也会发生。

保存数据需要依次与persistent store coordinator和store协调合作,从而对SQLite进行操作。当获取对象或者对象的值的时候,相对于从context获取而从store或者数据库去获取时相对昂贵的。存储变化时会有固定的消耗,不管多少变化发生,这里就会存在一个每个变化的平均消耗。当你改变了很多的情况下,你应该把这些变化打包进行保存。如果你每次变化都保存,那将是非常昂贵的,因为你必须经常去保存,但是极少去保存,SQLite的每次操作将会非常的大。

必须注意的时保存操作时原子性的,他们都是事物。要么所有的变化都被保存要么没有任何变化被保存。记住当实现自定义的NSIncrementalStore子类时要非常的小心,要保证要么所有的保存都失败(有冲突),要么所有的都保存。否则,store 和 context中的对象图形会不一致。

如果你用简单的一个context,保存一般不会出现失败。但是core data 允许每个persistent store coordinator对应多个context,所有有时候可能会造成冲突。保存是每个context,而另一个context会引入冲突的变化。core data 甚至允许完全独立的stack去对磁盘上相同的SQLite数据库进行操作。这种情况也很容易造成冲突(例如:一个context试图去改变另一个context已经删除的值)。另一个造成失败的原因是验证。core data 支持对象复杂的验证,这是一个深入的话题。简单的验证规则可以是:一个item的title不能长于300个字符。core data支持对属性进行复杂的验证。

结束语
 如果CoreData看起来令人害怕,这可能是因为它的灵活性允许你可以通过非常复杂的方法使用它。规则就是:保持事物越简单越耗,它会让开发变得更容易,并且把你和你的用户从麻烦中拯救出来。除非你确信它会带来帮助,才去使用更复杂的东西,比如说是background contex。

去使用一个简单的CoreData stack,并且使用我们在这篇文章中讲到的知识,你将很快会真正体会到Core Data能为你做什么,并且学到它是怎么缩短你开发周期的。



0 0