Core Data 是什么?
Core Data 是一个模型层的技术。Core Data 帮助你建立代表程序状态的模型层。Core Data 也是一种持久化技术,它能将模型对象的状态持久化到磁盘,但它最重要的特点是:Core Data 不仅是一个加载、保存数据的框架,它还能和内存中的数据很好的共事。
对象图管理(object graph management)是 Core Data 最强大的功能之一。
Core Data 是完全独立于任何 UI 层级的框架。它是作为模型层框架被设计出来的。
堆栈
当所有的组件都捆绑到一起的时候,我们把它称作 Core Data 堆栈,这个堆栈有两个主要部分。一部分是关于对象图管理,这正是你需要很好掌握的那一部分,并且知道怎么使用。第二部分是关于持久化,比如,保存你模型对象的状态,然后再恢复模型对象的状态。
在两个部分之间,即堆栈中间,是持久化存储协调器(persistent store coordinator),也被称为中间审查者。它将对象图管理部分和持久化部分捆绑在一起,当它们两者中的任何一部分需要和另一部分交流时,这便需要持久化存储协调器来调节了。
获取对象
这听起来可能非常不重要,但是在这个时候真正发生了很多事情。如果任何子对象偶然发生在内存中,Core Data 保证会复用那些对象。这是Core Data 独一无二的功能。在 context 内,从不会存在第二个相同的单一对象来代表一个给定的 item。
其次,持久化存储协调器有它自己内部对象值的缓存。如果 context 需要一个指定的对象(比如一个子 item),并且持久化存储协调器在缓存中已经有需要的值,那么,对象(即这个 item)可以不通过 store 而被直接加到 context。这很重要,因为访问 store 就意味着执行 SQL 代码,这比使用内存中存在的值要慢很多。
随着我们遍历 item 的子 item,以及子 item 的子 item,我们慢慢地把整个对象图引用到了 managed object context。而这些对象都在内存中之后,操作对象以及传递关系就会变得非常快,因为我们只是在 managed object context 里操作。我们跟本不需要访问持久化存储协调器。在我们的 Item 对象上访问 title,parent 和 children 是非常快而且高效的。
由于它会影响性能,所以了解数据在这些情况下怎么取出来是非常重要的。在我们特定的情况下,由于我们并没接触到太多的数据,所以这并不算什么,但是一旦你需要处理的数据量较大,你将需要了解在背后发生了什么。
当你遍历一个关系时(比如在我们例子中的 parent 或 children 关系)下面三种情况将有一种会发生:(1)对象已经在 context 中,这种操作基本上是没有任何代价的。(2)对象不在 context 中,但是因为你最近从 store 中取出过对象,所以持久化存储协调器缓存了对象的值。这个操作还算廉价(但是,一些操作会被锁住)。操作耗费最昂贵的情况是(3),当 context 和持久化存储协调器都是第一次访问这个对象,这种情况必须通过 store 从 SQLite 数据库取回。最后一种情况比(1)和(2)需要付出更多代价。
如果你知道你必须从 store 取回对象(比如你已经知道没有这些对象),当你限制一次取回多少个对象时,将会产生很大的不同。在我们的例子中,我们希望一次性取出所有子 items,而不是一个接一个。我们可以通过一个特别的技巧 NSFetchRequest。但是我们要注意,当我们需要做这个操作时,我们只需要执行一次取出请求,因为一次取出请求将会造成(3)发生;这将总是独占 SQLite 数据库的访问。因此,当需要显著提升性能时,检查对象是否已经存在将变得非常有意义。你可以使用-[NSManagedObjectContext objectRegisteredForID:]来检测一个对象是否已经存在。
改变对象的值
当我们插入一个新的 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 也支持通过属性进行复杂的验证策略。
我们需要先进行数据表的设计 —— 设计好的数据模型会以Managed Object Model的形式存在于内存中。采用面向对象的思想进行表的设计时,每一张表描述着一种实体(NSEntityDescription),一份NSManagedObjectModel则包含着多种NSEntityDescription。
一份NSManagedObjectModel,如cdNBA.xcdatamodel,可以包含多份NSEntityDescription,如Player、Team,而每一份NSEntityDescription有三种属性,分别是Attributes、Relationships和Fetched Properties。
根据NSEntityDescription创建出来的对象比较特殊,我们称之为NSManagedObject。由于它的特殊性,当我们要创建一个NSManagedObject对象时,比如Player实例,我们需要为其提供一个生存环境,称之为NSManagedObjectContext。