1.1 Core Data是什么
Core Data是个框架,它使得开发者可以把数据当成对象来操作,而不必在乎数据在磁盘中的存储方式。对于Objective-C程序员来说,这很有用,因为他们已经可以通过代码非常熟练地操作对象了。由Core Data所提供的数据对象叫做托管对象(managed object),而Core Data本身则位于你的应用程序和持久化存储区(persistent store)之间。持久化存储区是个通用的术语,指的是像SQLite数据库、XML文件(iOS不支持用XML文件作为持久化存储区)或Binary store(又名atomic store)这种数据文件。由于这些文件在底层硬件重启之后还会保留下来,所以它们是持久的。还有一种持久化存储方式,它的名字非常奇怪,叫做“In-Memory store”。虽说In-Memory store并不是“持久的”,但开发者在用它管理数据时却可以享受Core Data的所有优点,诸如变更管理与数据验证等,另外,其效率自然也是相当高的。
为了把数据从托管对象映射到持久化存储区中,Core Data需要使用托管对象模型,而开发者则可以通过对象图(object graph)来配置应用程序的数据结构。可以把对象图想象成一系列“饼干模型切割刀”(cookie cutter),而托管对象正是用这些切割刀切出来的。对象图里的对象指的是实体,每个实体就好比一把“饼干模型切割刀”,用于制作自定义的托管对象。有了托管对象之后,就可以直接在Objective-C里面操作它们,而无需再编写SQL代码了(笔者假定你使用SQLite作为持久化存储区,因为这是最常用的一种持久化存储方式)。当把数据保存到磁盘的时候,Core Data显然会把这些托管对象映射回持久化存储区里面。
托管对象持有一份对持久化存储区里相关数据的拷贝。如果用数据库作为持久化存储区,那么托管对象可能对应于数据库里某张数据表中的一行。如果用XML文件作为持久化存储区(此方式只有Mac系统支持),那么托管对象可能对应于某个数据元素(data element)里面的一份数据。托管对象可以是NSManagedObject类的实例,但一般情况下,它都是某个NSManagedObject子类的实例。这个问题将在第2章中详细讨论。
所有托管对象都必须位于托管对象上下文(managed object context)里面,而托管对象上下文又位于高速的易失性存储器里面,也就是位于RAM中。为什么需要有托管对象上下文呢?原因之一就是在磁盘与RAM之间传输数据时会有开销。磁盘读写速度比RAM慢得多,所以不应该频繁访问它。而有了托管对象上下文之后,对于原来需要读取磁盘才能获取到的数据,现在只需访问这个上下文,就可以非常迅速地获取到了。但它的缺点在于,开发者必须在托管对象上下文上面定期调用save:方法,以将变更后的数据写回磁盘。托管对象上下文的另一个功能是记录开发者对托管对象所做的修改,以提供完整的撤销与重做支持。
提示 “如果不能把一件事用简单的话说清楚,那就表明你理解得还不够透彻。”这是先贤阿尔伯特·爱因斯坦的一句名言。本书每章均以爱因斯坦的名言开头。Core Data是个比较难学的技术,但这并不是说我们不能把它分解成多个易于理解的小知识点。笔者在编写技术教程和文档的时候,都会遵照爱因斯坦的教诲,尽量用比较好懂的方式把它们写出来,同时也尽量会把内容写得丰富一些。
图1-1直观地描述了Core Data的几个主要概念。
图1-1 Core Data概览
1.1.1 持久化存储协调器
图1-1左侧的持久化存储协调器(persistent store coordinator)里面包含一份持久化存储区,而存储区里面又含有数据表里的若干行数据。设置持久化存储协调器的时候,我们通常选用SQLite数据库作为持久化存储区。另外,也可以选用Binary、XML或In-Memory等形式的持久化存储区。但要注意,Binary和XML格式的存储区是“原子的”(atomic),也就是说,即便你只想修改少量数据,在保存的时候也依然需要把整个文件都写入磁盘。首次将原子的存储区(atomic store)读入内存时当然也会有这个问题。如果数据很多,那么使用这种存储区的问题就比较严重了,因为它会占据宝贵的内存空间。
与原子存储不同,SQLite数据库会在用户提交变更日志时进行增量更新,变更日志也叫做事务日志。由于采用了这种更新方式,所以SQLite数据库的内存占用量相对来说非常小。有鉴于此,开发者一般都会选用SQLite数据库,尤其在把Core Data集成到iCloud的时候,更应该如此。
提示 持久化存储区只应该由Core Data来创建。不应该让Core Data去使用不是由它所创建的数据库。假如需要使用既有的数据,那么应该将其导入Core Data。这个问题放在第8章讨论。
同一个持久化存储协调器可以有多个持久化存储区。把Core Data与iCloud相集成的时候,就可能会出现这种情况。我们可以把不属于iCloud的数据放在一个存储区里,而把属于iCloud的数据放在另一个存储区里。这样既能节省网络带宽,又能节省iCloud存储空间。即便你有两个持久化存储区,也不意味着必须使用两种对象图。Core Data的模型配置允许开发者使用多个独立的存储区,但却采用同一套对象图。在设定Core Data的模型配置选项时,可以指明对象图里的某一部分属于哪个持久化存储区。假如确实想使用多个持久化存储区,那么就不能为这些存储区之间的数据建立“关系”了。Core Data的配置问题放在第15章讨论。
要想创建持久化存储区,需生成NSPersistentStore类的实例;要想创建持久化存储协调器,需生成NSPersistentStoreCoordinator类的实例。
1.1.2 托管对象模型
图1-1的中部是托管对象模型,它位于持久化存储协调器和托管对象上下文之间。顾名思义,托管对象模型是描述数据结构的模型或图示(graphical representation),而托管对象正是以它为基础产生出来的。它与数据库模式(database schema)相似,有时也叫做对象图。要想创建托管对象模型,可以用Xcode来配置实体及实体之间的关系。实体类似于数据库中的数据表模式(table schema)。实体本身并不包含数据,它们只是规定了基于该实体的托管对象应该具有何种特性。实体就是刚才提到的那种“饼干模型切割刀”,正如数据库里的数据表有字段(field)一样,实体也有属性(attribute)。属性的数据类型可以是整数(integer)、字符串(string)或日期(date)。第2章与第4章将会详述这些问题。
要想创建托管对象模型,需生成NSManagedObjectModel类的实例。
1.1.3 托管对象上下文
图1-1的右侧是托管对象上下文,其中包含多个托管对象。托管对象上下文负责管理其中对象的生命期(lifecycle),并且负责提供许多强大的功能,诸如faulting、变更追踪(change tracking)、验证(validation)等。所谓faulting,意思就是用户从持久化存储区中获取数据时,系统只会把需要用到的那一部分获取过来。第10章将详细讨论faulting。变更追踪用于支持撤销及重做功能。验证机制用来确保由托管对象模型所订立的规则。比方说,可以针对实体的单个属性来限定其最小值或最大值,这将在第2章中讨论。
持久化存储区可以有很多个,与之类似,托管对象上下文也可以不止一个。有时我们需要在后台处理任务(比方说把数据保存到磁盘或导入数据),这种情况下可以采用多个上下文。假如在前台上下文(foreground context)上面调用save:,那么用户界面就可能会有“卡顿”(lag)现象,尤其当数据变化较大的时候更是如此。要想避免这个问题,有个简单的办法就是只在用户按下手机Home键时才去调用save:,这时应用程序会转入后台。还有个稍微复杂但却更加灵活的办法,就是采用两个托管对象上下文。请记住,托管对象上下文是存放在高速内存里面的。你可以配置其中一个上下文,令其把数据保存到另一个上下文里。一旦把前台上下文中的数据保存到后台上下文,那么就可将后台上下文中的数据异步地(asynchronously)存入磁盘。这种分段式的做法可以确保磁盘写入操作不会影响用户界面的流畅度。
从iOS 5开始就可以配置多个上下文之间的上下级关系了。子上下文会将它的父上下文视为持久化存储区,而这个父上下文实际上是用来处理各项繁重操作的(例如在后台保存数据等)。这个问题放在第11章深入讨论。
要想创建托管对象上下文,需生成NSManagedObjectContext类的实例。