Core Data 概述

相关扩展原文 :http://objccn.io/issue-4-2/   http://objccn.io/issue-4-3/

Core Data 可能是 OS X 和 iOS 里面最容易被误解的框架之一,为了帮助大家理解,我们将快速的研究 Core Data,让大家对它有一个初步的了解,对于想要正确使用 Core Data 的同学来说,理解它的概念是非常必要的。几乎所有对 Core Data 感到失望的原因都是因为对它工作机制的错误理解。让我们开始吧:

Core Data 是什么?

大概八年前,2005年的四月份,Apple 发布了 OS X 10.4,正是在这个版本中 Core Data 框架发布了。那个时候 YouTube 也刚发布。

Core Data 是一个模型层的技术。Core Data 帮助你建立代表程序状态的模型层。Core Data 也是一种持久化技术,它能将模型对象的状态持久化到磁盘,但它最重要的特点是:Core Data 不仅是一个加载、保存数据的框架,它还能和内存中的数据很好的共事。

如果你之前曾经接触过 Object-relational maping (O/RM):Core Data不是一个 O/RM,但它比 O/RM 能做的更多。如果你之前曾经接触过 SQL wrappers:Core
Data 不是一个 SQL wrapper。它默认使用 SQL,但是,它是一种更高级的抽象概念。如果你需要的是一个 O/RM 或者 SQL wrapper,那么 Core Data 并不适合你。

对象图管理(object graph management)是 Core Data 最强大的功能之一。为了更好利用 Core Data,这是你需要理解的一块内容。

还有一点要注意:Core Data 是完全独立于任何 UI 层级的框架。它是作为模型层框架被设计出来的。在 OS X 中,甚至在一些后台驻留程序中,Core Data 都起着非常重要的意义。

堆栈

Core Data 有相当多可用的组件。这是一个非常灵活的技术。在大多数的使用情况下,设置都相当简单。

当所有的组件都捆绑到一起的时候,我们把它称作 Core Data 堆栈,这个堆栈有两个主要部分。一部分是关于对象图管理,这正是你需要很好掌握的那一部分,并且知道怎么使用。第二部分是关于持久化,比如,保存你模型对象的状态,然后再恢复模型对象的状态。

在两个部分之间,即堆栈中间,是持久化存储协调器(persistent store coordinator),也被称为中间审查者。它将对象图管理部分和持久化部分捆绑在一起,当它们两者中的任何一部分需要和另一部分交流时,这便需要持久化存储协调器来调节了。

对象图管理是你程序模型层的逻辑存在的地方。模型层的对象存在于一个 context 内。在大多数的设置中,存在一个 context ,并且所有的对象存在于那个 context 中。Core Data 支持多个 contexts,不过对于更高级的使用情况才用。注意每个 context 和其他 context 都是完全独立的,一会儿我们将会谈到。需要记住的是,对象和它们的 context 是相关联的。每个被管理的对象都知道自己属于哪个
context,并且每个 context 都知道自己管理着哪些对象。

堆栈的另一部分就是持久了,即 Core Data 从文件系统中读或写数据。每个持久化存储协调器(persistent store coordinator)都有一个属于自己的持久化存储(persistent store),并且这个 store 在文件系统中与 SQLite 数据库交互。为了支持更高级的设置,Core
Data 可以将多个 stores 附属于同一个持久化存储协调器,并且除了存储 SQL 格式外,还有很多存储类型可供选择。

最常见的解决方案如下图所示:

组件如何一起工作

让我们快速的看一个例子,看看组件是如何协同工作的。在我们的文章《一个完成的 Core Data 应用》中,正好有一个实体,即一种对象:我们有一个 Item 实体对应一个 title。每一个
item 可以拥有子 items,因此,我们有一个父子关系。

这是我们的数据模型。正如我们在《数据模型和模型对象》一文中提到的一样,在 Core Data 中有一种特别的对象——实体。在这种情况下,我们只有一个实体:Item 实体。同样的,我们有一个 NSManagedObject 的子类,叫做 Item。这个 Item 实体映射到Item 类上。在数据模型的文章中会详细的谈到这个。

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

当程序运行时,我们像上面图片描绘的一样设置我们的堆栈,一个存储,一个 managed object context,以及一个持久化存储协调器来将它们关联起来。

在第一次运行时,我们并没有任何 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 = [Item insertNewObjectInManagedObjectContext:managedObjectContext];

现在,在我们的 managed object context 中有一个唯一的 item。Context 知道这是一个新插入进来需要被管理的对象,并且被管理的对象 rootItem 知道这个 Context(它有一个 -managedObjectContext 方法)。

保存改变

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

NSError *error = nil;
if (! [managedObjectContext save:&error]) {
    // 啊,哦. 有错误发生了 :(
}

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

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

更新关系

Core Data 的优势在于管理关系。让我们着眼于简单的情况:增加我们第二个 item,并且使它成为 rootItem 的子 item:

Item *item = [Item insertNewObjectInManagedObjectContext:managedObjectContext];
item.parent = rootItem;
item.title = @"foo";

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

获取对象

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

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

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setURL:rootItem.objectID.URIRepresentation forKey:@"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];

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

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

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

NSOrderedSet *children = rootItem.children;

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

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

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

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

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

当你遍历一个关系时(比如在我们例子中的 parent 或 children 关系)下面三种情况将有一种会发生:(1)对象已经在
context 中,这种操作基本上是没有任何代价的。(2)对象不在 context 中,但是因为你最近从 store 中取出过对象,所以持久化存储协调器缓存了对象的值。这个操作还算廉价(但是,一些操作会被锁住)。操作耗费最昂贵的情况是(3),当 context 和持久化存储协调器都是第一次访问这个对象,这种情况必须通过 store 从 SQLite 数据库取回。最后一种情况比(1)和(2)需要付出更多代价。

如果你知道你必须从 store 取回对象(比如你已经知道没有这些对象),当你限制一次取回多少个对象时,将会产生很大的不同。在我们的例子中,我们希望一次性取出所有子 items,而不是一个接一个。我们可以通过一个特别的技巧 NSFetchRequest。但是我们要注意,当我们需要做这个操作时,我们只需要执行一次取出请求,因为一次取出请求将会造成(3)发生;这将总是独占
SQLite 数据库的访问。因此,当需要显著提升性能时,检查对象是否已经存在将变得非常有意义。你可以使用-[NSManagedObjectContext
objectRegisteredForID:]
来检测一个对象是否已经存在。

改变对象的值

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

item.title = @"New title";

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

从最后一次保存开始,context 知道哪些对象被插入,改变以及删除。你可以通过 -insertedObjects-updatedObjects,
以及 –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 的 title 不能超过300个字符。但是
Core Data 也支持通过属性进行复杂的验证策略。

结束语

如果 Core Data 看起来让人害怕,这最有可能是因为它的灵活性允许你可以通过非常复杂的方法使用它。始终记住:尽可能保持简单。它会让开发变得更容易,并且把你和你的用户从麻烦中拯救出来。除非你确信它会带来帮助,才去使用更复杂的东西,比如说是 background contexts。

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

时间: 2024-11-10 21:23:38

Core Data 概述的相关文章

ios开发:Core Data概述

Core Data 概述 2005年的四月份,Apple 发布了 OS X 10.4,在这个版本中 Core Data 框架发布了.Core Data本身既不是数据库也不是数据库访问框架.相反,Core Data是一个完整的数据模型解决方案.可以简单理解为对持久层的封装,使得我们可以通过可视化建立数据模型,简化数据存取.即使不懂SQL语句,也依然可以使用Core Data.因为Core Data将底层的数据库SQL语句封装成了一套API,并可通过可视化操作来建立数据库的模型和表之间的关系,它甚至

Core Data系列译文:Core Data概述

在"伯乐在线"发现了一篇很不错的关于CoreData的文章,只是那里不能收藏,故而转载至此 以下是文章原文: 本文由 伯乐在线 - IvyShao 翻译自 Daniel Eggert.欢迎加入技术翻译小组.转载请参见文章末尾处的要求. [2013-11-19 21:24:26 伯乐在线补充]:由于之前和译者@shaojingkk 沟通不够,我们在发布这篇译文之前并不知道译者参考对照@answer-huang 之前的译文版本,所以发布文章时未能在文中特别说明,责任在我们,在此向@answ

深入理解Core Data

留给我这忘事精看 Core Data 是什么? 大概八年前,2005年的四月份,Apple 公布了 OS X 10.4,正是在这个版本号中 Core Data 框架公布了.那个时候 YouTube 也刚公布. Core Data 是一个模型层的技术.Core Data 帮助你建立代表程序状态的模型层.Core Data 也是一种持久化技术,它能将模型对象的状态持久化到磁盘,但它最重要的特点是:Core Data 不仅是一个载入.保存数据的框架,它还能和内存中的数据非常好的共事. 假设你之前以前接

Core Data

简介 Core Data是iOS5之后才出现的一个框架,它提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成OC对象.在此数据操作期间,我们不需要编写任何SQL语句,这个有点类似于著名的Hibernate持久化框架,不过功能肯定是没有Hibernate强大的.简单地用下图描述下它的作用: 左边是关系模型,即数据库,数据库里面有张person表,person表里面有id.name.age三个字段,而且有2条记录: 右

我为什么用 SQLite 和 FMDB 而不用 Core Data

转:http://segmentfault.com/a/1190000000363392 编者注:文章的"我"是指原作者. 凭良心讲,我不能告诉你不去使用Core Data.它不错,而且也在变好,并且它被很多其他Cocoa开发者所理解,当有新人加入你的组或者需要别人接手你的项目的时候,这点很重要.更重要的是,不值得花时间和精力去写自己的系统去代替它.真的,使用Core Data吧. 为什么我不使用Core Data Mike Ash写到: 就我自己而言,我不是个狂热粉丝.我发现API是

Core Data存储自定义类型数据

目录: 一.使用CoreData存储基本数据 二.使用CoreData存储自定义类型数据 简单介绍CoreData CoreData是iOS编程中使用持久化数据存储的一种方式,我们知道CoreData并不是数据库本身,而是Apple提供的对象持久化技术--Object Persistent technology.CoreData框架为我们的数据变更.管理.对象存储.读取和恢复提供了支持.下面我们来尝试创建一个简单的CoreData Project. 操作 1. 打开x-code,为你的proje

Core Data使用之一(Swift): 保存

Core Data 用于永久化数据,它是基于SQLite数据库的保存一门技术. 那么,在Swift中,它是如何实现的呢? 首先,需要新建一个模板,打开工程中的xcdatamodeld文件,点击“Add Entity” ,这时候,就创建的一个模板.之后,可以修改模板的名称为自己想要的名称.然后,在Attributes里面,点击“+”,添加字段并修改类型. 然后,在代码里面 “import CoreData”.接着,用NSManagedObject来保存对象,它可以转换成任何对象.它的类型是字典.

Chapter 23 Core Data

1.  Core Data is only able to store certain data types in its store, and UIImage is not one of these types. Instead, you declared the UIImage as transformable. With a transformable attribute, Core Data will convert the object into NSData when saving,

Core Data浅谈系列之十 : 关于数据模型中实体的属性

之前写了<Core Data浅谈系列汇总>,今天稍微回顾了下,做些补充. 在这个系列的第一篇<基础结构>中(2013年1月份的文章,时间过得好快啊!),有简单带过Entity的Attribute: 数据类型.布尔值统一用NSNumber来表示: 字符串类型用NSString表示: 时间类型用NSDate表示: 二进制数据类型用NSData表示: 非标准类型用Transformable来表示: 而Attribute还有其自身的Properties,比如Transient表示不用持久化