本文由海水的味道编译整理,请勿转载,请勿用于商业用途。
当前版本号:0.4.0
第二章 Core Data入门
本章将讲解Core Data框架中涉及的基本概念,以及一个简单的Core Data app的结构组成。
首先回忆一下,在你还没有使用Core Data之前,你是如何处理数据的持久化。
将对象持久化到磁盘
当你需要在程序中将数据保存到磁盘,通常你会创建一个对象容器,可能是数组、集合或字典。当在保存数据时,你会将对象编码或序列化,然后保存到二进制文件。对于数据量较小的情况,你可能也会选择.plist文件保存数据。
作为一种将数据保存到二进制文件的替代方法,在Core Data引入iOS之前,开发者也可以直接使用SQLite,它是一个简单且非常轻量级的数据库。SQLite自iPhone OS的早期版本开始就可用于iOS设备。当你在开发中需要使用包含大量对象的容器时,保存数据到数据库对数据的查询速度非常有帮助。
SQLite,顾名思义,它是基于结构化查询语言SQL。通过发送如insert或select(提取)SQL指令与数据库进行进行交流。不需担心提取某个对象而导致整个二进制文件都会载入内存的问题。
使用SQLite的不足时是:你需要使用大量的过程式C语言API,编写繁复的数据访问代码。为了保存一个对象到SQLite数据库,你需要编写包含INSERT语句的字符串的代码。该字符串由对象的实例变量值组成,而在传入字符串到最终的C语言函数之前,还需要将字符串转换成C语言风格的字符串。
遇见Core Data
相对于SQLite,Core Data集合了面向对象数据库在对象序列化方面的速度与效率的优势。
实体与NSManagedObject对象
在传统的数据库中,数据表定义了数据的结构;在面向对象语言中,类描述了数据的结构;在Core Data框架中,描述数据的结构则是实体。一个实体由属性和关系组成。你通过使用Xcode的数据模型设计器创建实体,并为实体指定属性和关系。你可以通过如下方式实体创建一个NSManagedObject对象。
NSEntityDescription *patientEntity =
[NSEntityDescription entityForName:@"Patient"
inManagedObjectContext:context];
NSManagedObject *object = [[NSManagedObject alloc]
initWithEntity:patientEntity insertIntoManagedObjectContext:context];
对于NSManagedObject对象,你可以使用KVC方式访问对象的属性,类似代码清单2.1所示。
代码清单2.1 访问一个NSManagedObject对象的属性
NSManagedObject *aPatientObject; // 假设对象已填充正确数据
NSString *firstName = [aPatientObject valueForKey:@"firstName"];
NSString *lastName = [aPatientObject valueForKey:@"lastName"];
[aPatientObject setValue:@"Pain killers" forKey:@"currentMedication"];
[aPatientObject setValue:@"Headache" forKey:@"currentIllness"];
你也可以使用一个暴露了访问器方法或属性的自定义NSManagedObject子类。这样你就可以使用类似于代码清单2.2那样的方式访问属性。你将在第六章“使用NSManagedObject对象”中了解到更多的细节。
代码清单2.2 使用一个自定义的NSManagedObject子类。
Patient * aPatientObject; // 假设对象已填充正确数据
NSString *firstName = [aPatientObject firstName];
NSString *lastName = aPatientObject.lastName;
[aPatientObject setCurrentMedication:@"Pain killers"];
aPatientObject.currentIllness = @"Headache";
你仍然可以使用valueForKey:的形式访问对象属性,但是使用KVC要比使用访问器效率低一点。 只在必要时使用KVC,比如你需要动态选择key或keyPath。
[newEmployee setValue:@”Stig” forKey:@”firstName”];
[aDepartment setValue:@1000 forKeyPath:@”manager.salary];
NSManagedObjectC对象上下文(Managed Object Contexts)
当你使用NSManagedObject对象,实际上你是在一个特定的上下文中进行操作,该上下文被称为NSManagedObject对象上下文。上下文就像一个容器,容器内存放这从持久化存储文件中载入的NSManagedObject对象。上下文时刻保持对容器内的NSManagedObject对象变化的的追踪。
从概念上讲,上下文好比是桌面平台的一个文档对象,文档负责显示存储在磁盘上的数据。当文档被打开,数据将会从磁盘上被加载并显示在屏幕上。上下文保持对文档变化的追踪。文档数据被上下文持有在内存中,当数据被保存,上下文负责将变化写入到磁盘。
NSManagedObject对象上下文(Managed Object Context 简称MOC)以类似的方式工作。当需要数据时,它负责从存储区中提取(fetch)数据。并在内存中时刻保持对这些对象的变化的追踪,最后当它们被保存到磁盘时,MOC将这些改变写入磁盘。除非你命令MOC执行保存操作,否则你在上下文中对任何NSManagedObject对象所做的任何改变都是临时的,并不会影响到磁盘上的数据。
与常规的文档对象不同,在某一时刻,你可以使用不止一个NSManagedObject对象上下文。即使它们都关联于同一个底层数据。比如你可能加载同一位病人对象到两个不同的上下文中。并对其中一个对象进行修改。如图2.2所示。另一个对象的上下文将不会受这些修改的影响。除非你选择保存第一个上下文,此时将会有一个通知被发送以提醒你另一个上下文已经修改了数据。如若需要,你可以重新加载第二个上下文。
图2.2 NSManagedObject对象上下文和它的NSManagedObject对象
虽然在iOS平台多个上下文的使用情况要比桌面平台少见,如果你在后台处理NSManagedObject对象,例如从在线资源获取数据并保存到app的本地数据存储区,那么你就需要使用一个单独的上下文;如果你需要使用NSManagedObject对象上下文提供的自动撤销功能,你也需要使用一个单独的上下文处理这些需要撤销的NSManagedObject对象。撤销对单个属性的所有改变被看作是一次单独的动作。当保存对象到主上下文时,保存所有这些改变的行为将被算做是主上下文中的一次撤销动作。当用户需要时,允许用户一次性撤销所有的改变。你将在随后的章节看到这样的案例。
NSPersistentStore与NSPersistentStoreCoordinator
底层数据会被持有在磁盘的一个持久化存储文件中。对于iOS设备,通常指SQLite存储区。你也可以选择使用二进制存储方式或你自己定义的原子存储方式,但这要求整个对象图被载入到内存。在内存有限的设备上,这很快会是一个问题。
你从不需要直接和持久化存储文件交流,你不需要担心如何存储数据。相反,你通过NSPersistentStoreCoordinator对象存储数据。你可以把NSPersistentStoreCoordinator看做是一个数据库连接。一个NSPersistentStoreCoordinator对象被作为NSManagedObject对象上下文的中介;可能一个NSPersistentStoreCoordinator对象要与多个持久化存储文件交互。这意味着NSPersistentStoreCoordinator对象要将这些持久化存储文件联合在一起,以暴露给NSManagedObject对象上下文访问。
如图2.3所示。一个NSPersistentStoreCoordinator对象包含了一个持久化存储文件。当你设置了一个NSPersistentStoreCoordinator对象,你通常会选择SQLite作为存储文件的存储类型。除了SQLite储存类型以外,你还可以选择二进制存储类型,XML存储类型以及驻内存存储类型。需要注意的是二进制和XML存储类型都是原子性的,这意味着即使你只是修改了少量数据,在保存数据时,你也需要将整个文件写入磁盘。相对的,当你读取数据时,也需要将整个数据文件读入内存。
通常你不需要过多的担心持久化存储文件和NSPersistentStoreCoordinator对象,除非你想处理多个存储区或定义你自己的存储方式。
图2.3 Core Data结构
通常你不需要过多的担心持久化存储文件和NSPersistentStoreCoordinator对象,除非你想处理多个存储区或定义你自己的存储方式。
NSManagedObjectModel对象
在图2.3中,一个NSManagedObjectModel对象处在NSPersistentStoreCoordinator对象和NSManagedObject对象上下文之间。Core Data根据NSManagedObjectModel对象确定如何将底层的持久化文件中的数据映射为NSManagedObject对象。一个NSManagedObjectModel对象用于表示数据的结构。NSManagedObjectModel对象也被称为对象图(object graph)。
关系
数据模型设计器也是定义实体间关系的地方。比如一个Patient对象会有一个指向Doctor对象的一对一关系,同样,Doctor对象也会有一个指向Patient对象的一对多关系。如图2.1所示。
图2.1 病人和医生间的关系
当对关系进行建模时,通常会使用到关系型数据库的术语,比如一对一、一对多或多对多。在图2.1中,一位病人由一位医生负责,而一位医生需要负责多位病人,所以医生-病人的关系是一对多。
如果医生-病人关系是一对多的,那么反转(inverse)关系(病人-医生)就是多对一。当你在数据模型设计器中建模这些关系时,这两种正反关系你都需要显示地设置。通过显式地设置反转关系,Core Data就会自动地维护数据的完整性。如果你给一位病人指派了一位医生,这位病人也将被自动的添加到医生的病人列表中。
为每一个关系指定一个名字,以使得关系能够和实体的属性那样被访问。同样,你也可以使用KVC方法或者在自定义子类中声明的访问器和属性来访问关系,类似代码清单2.3那样。
代码清单2.3 访问对象关系
Patient *aPatientObject; // 假设对象已填充正确数据
Doctor *aDoctorObject = [aPatientObject valueForKey:@"doctor"];
Patient *anotherPatientObject;
anotherPatientObject.doctor = aDoctorObject;
NSLog(@"Doctor's patients = %@", [aDoctorObject patients]);
/* 输出
Doctor's patients = (aPatientObject, anotherPatientObject, etc...)
*/
需要注意一点: Core Data不会维护任何存放对象的集合(NSArray,NSSet,NSDictonary)内元素的次序,包括一对多关系。稍后你将在书中看到对象返回的次序可能不会和输入的次序一样。如果次序非常重要,你需要自己来追踪次序,可以为每个对象设置一个升序的数值索引属性。如果你使用过MySQL,PostgreSQL或MS SQL Server数据库,你可能给每个数据库中的记录一个唯一id。而使用Core Data,你不需要任何类型的唯一标识Id,也不需要处理表连接。Core Data将在后台自动处理。你所需要做的就是定义对象间的关系。Core Data框架将在后台决定如何生成最佳的底层机制。
对于医生-病人的一对多关系,如果你想往关系patients集合中加入一个Patient对象。你可以使用如下代码:
NSMutableSet *patients = [aDoctor mutableSetValueForKey:@”patients”];
[patients addObject:newPatient];
[patients removeObject:oldPatient];
// 或
[aDoctor addPatientObject:newPatient];
[aDoctor removePatientObject:oldPatient];
不要使用点语法获取集合,因为点语法获得的值是NSSet,而不是NSMutableSet类型:
[aDoctor patients] addObject:newPatient];
[aDoctor.patients addObject:newPatient];
提取对象
NSManagedObject对象上下文也是你使用NSFetchRequest对象从磁盘提取对象的媒介。一个NSFetchRequest对象必须包含一个NSEntityDescription对象,用来指定哪个实体的对象需要被检索。如果你想从持久存储区中提取所有的病人记录,你需要创建一个NSFetchRequest对象,指定Patient实体为被检索对象,并告诉MOC执行提取请求。MOC返回一个数组形式的结果给你。同样,数组内元素的次序可能与你存储时的次序不一致,也可能你下一次执行提取请求得到的结果和现在得到的结果次序也不一样,除非你对提取到结果按特定的规则再做一次排序。
为了提取特定的或满足特定条件的对象,你可以为NSFetchRequest对象配置一个NSPredicate对象;为了使用特定次序对结果进行排序,你可以再为NSFetchRequest对象配置一个NSSortDescriptor对象数组。你可能选择获取某位医生的所有病人记录,并按lastName属性进行排序。如果你在排序之前为所有的病人对象设置了一个数值索引属性,你可以要求提取结果按索引属性进行排序,以使得每次它能以相同的次序返回。
惰性加载(Faulting)与唯一性
Core Data使用了一种称为惰性加载(faulting)的技术,尽力优化性能并保持最小的内存使用率。
fault是一个很让人迷惑的一个词汇,苹果官方文档对Fault的定义:一个fault是一个占位符对象,用于表示一个还没有被完整实例化的NSManagedObject对象或是一个代表关系的容器对象。所以Core Data中的fault更接近于Page Faults的概念。翻译为惰性加载并不妥当,但是相对比较形象。
考虑这样的情形:如果你加载了一个Patient记录到内存中;为了访问该Patient对象关联的Doctor关系,Doctor对象也会被加载。如果你又需要访问该Doctor对象负责的所有其他病人,那么所有与该Doctor对象关联的Patient对象就会全部加载到内存中。伴随这样的行为,提取单个对象的行为最终会演变成提取成百上千的对象——每一个相关联的对象都会被提取,直至可能整个数据库被载入内存。
为了解决这个问题,Core Data将返回给你的NSManagedObject对象的属性和关系都标记为惰性(fault),这样对象的实际数据就不会被载入到内存。如果你尝试访问其中一个属性或关系,比如访问某位病人的医生名字,惰性将被消除,Core Data会为你提取期望对象的值。类似地,最新提取到的Doctor对象的关系也会被设置为惰性,当你需要访问该关系指向的任何相关的对象,惰性就会被消除。所有这些都是自动发生的,不需要让你担忧。当然,你也可以将那些暂时不用的对象重新打回惰性状态以减少内存占用。
在代码调试中,你可能需要查看提取到的结果集中是否存在特定的对象。如图2.2所示。因为Core
Data会启用惰性加载机制。所以你得到的会是类似图2.2所示的结果。
图片显示较小,可拖拽到桌面查看:-]
图2.2 默认的惰性机制
为了查看结果,你可以禁用Core Data默认返回惰性对象的设置。使用如下代码:
request.returnsObjectsAsFaults
= NO;
为了保持代码的整洁,根据上述代码设置一个断点动作,如图2.3所示。
图2.3 强制返回结果为非惰性
上述设置只是针对属性,如果你有一个关系,使用如上方法,你将得到一个“relationship
fault”。
所以为了查看关系的值,你可以使用类似下述代码:
p [team.members count]
这将消除关系的惰性状态。如果你想禁用关系的默认的惰性设置,以获得预提取的效果。使用如下代码:
[request
setRelationshipKeyPathsForPrefetching:@[@"team"]];
当一个NSManagedObject对象已经被载入,那么NSManagedObject对象所处的当前上下文会确保在随后的提取操作中,总是返回已经存在的实例,考虑代码清单2.4。
代码清单2.4 提取唯一对象
Patient *firstPatient;
Doctor *firstPatientsDoctor = firstPatient.doctor;
Patient *secondPatient;
Doctor *secondPatientDoctor = secondPatient.doctor;
/*
* 如果两个病人对应的医生都是同一人,那么当各个病人关系上的惰性被消除后,返回的医生实例都会是同一个实例。
*/
if (firstPatientsDoctor == secondPatientsDoctor) {
NSLog(@"Patients share a doctor!");
}
这被称为唯一性。比如访问一个特定的Patient对象,在任何NSManagedObject对象上下文中,你都只会得到同一个对象实例。
一个NSManagedObject对象关联一个上下文。在一个给定的上下文中,一个NSManagedObject对象表示持久化存储文件中的一条记录。在一个给定的上下文中,持久化存储文件中的一条记录只对应一个NSManagedObject对象,但有可能存在多个上下文,每个上下文都有一个关联同一个存储记录的NSManagedObject对象。换句话说,一个NSManagedObject对象和一条存储记录之间的关系是一对一的;一个存储区记录和NSManagedObject对象之间的关系是多对一的。
研究Xode内置的Core Data模板
现在你已经对Core Data术语有了一个很好的概念,接下来,我们将学习如何利用XCode提供的内置Core Data模板构建一个基于Core Data的iOS app。
基于导航的项目模板
Xcode中预置Core Data配置的项目模板有:Master-Detail Application,Utility Application以及Empty Application。
图2.4 新建项目的窗口
启动XCode,选择File>New>New Project(Shift+Command+N快捷键),接着选择Master-Detail Application模板,项目取名为TemplateProject,然后勾选Use Core Data复选框,如图2.4所示。
勾选“Use Core Data”选项后项目会多出一些额外项。首先,项目引用了CoreData.framework,并创建了一个TemplateProject.xcdatamodeld文件,该文件定义了数据模型结构,你可以使用XCode内置的可视化建模工具进行构建。
点击TemplateProject.xcdatamodeld文件。在XCode中有两种方式查看数据模型,Table和Graph方式,如图2.5所示。
图2.5 Xcode数据模型设计器的两类编辑风格
数据模型设计器
在编辑器的左上方,你将看到一个实体列表。在模板文件中,有一个Event的实体。如果你使用Table编辑器风格显示选择的实体,你会看到实体的Attributes列表,Relationships列表以及Fetched Properties列表。
Event实体列出了一个叫做timeStamp属性。如果你点击选择这个属性,然后打开XCode 的数据模型检视器(Data Model inspector)(按住Option+Command+3快捷键)。你将看到它的Type被设置成了Date。
检视器提供了一些关于属性设置的选项。比如你可以选择在数据存储时验证数据,或者让属性为可选;这些选项会在第三章“数据建模”进行讨论。
XCode的Graph编辑器风格对你的对象模型实体进行了可视化呈现。现在还只有一个实体,当实体数量不止一个,实体之间可以使用线和箭头进行关系连接。如图2.6所示。
图2.6 对象图中对象之间的关系
设置Core Data栈
当使用持久化存储文件中的数据时,你需要构建一个对象栈;栈的底部是实际存放在磁盘上的持久化存储文件,接着是NSPersistentStoreCoordinator对象,它将持久化存储文件和它的下一级NSManagedObject对象上下文连接在了一起。如图2.7所示。
图2.7 Core Data栈
在NSPersistentStoreCoordinator对象的底部可能有多个持久化存储文件和多个NSManagedObject对象上下文。
让我们来研究一下用来设置Core Data栈的模板代码。打开AppDelegate.h文件,你会发现文件内定义了一些属性声明。代码清单2.5所示。
代码清单2.5 AppDelegate.h中设置Core Data栈的模板代码
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
@end
app代理负责保持对NSManagedObjectModel对象的追踪,NSManagedObjectModel对象模型的信息包含在TemplateProject.xcdatamodeld文件中[1]。AppDelegate有一个指向NSPersistentStoreCoordinator对象的引用,以及一个指向NSManagedObject对象上下文的引用。applicationDocumentsDirectory被用于确定数据保存的磁盘位置。
切换到AppDelegate.m文件,滚动到文件的底部,你会看到一个applicationDocumentsDirectory方法,该方法返回app的documents目录的路径。
接下来找到persistentStoreCoordinator方法。为了访问documents目录中的SQLite存储文件TemplateProject.sqlite,该方法设置了一个NSPersistentStoreCoordinator对象,并使用managedObjectModel方法提供的模型来初始化持久存储。
接下来找到managedObjectModel方法,该方法返回一个根据TemplateProejct.momd的文件创建的NSManagedObjectModel对象。当你编译项目时,TemplateProject.xcdatamodeld数据模型将被编译成.momd资源,并且保存到app的Bundle目录。
通过使用NSManagedObjectModel的类方法mergedModelFromBundles方法合并所有可用的模型文件或使用modelByMergingModels:方法合并特定的文件来创建一个NSManagedObjectModel都是可以的。虽然在当前项目中只有一个模型文件,但是为了对模型进行分类管理而创建多个.xcdatamodeld文件是可行的。
找到managedObjectContext方法。该方法使用persistentStoreCoordinator方法返回的NSPersistentStoreCoordinator对象配置上下文。现在你在application: didFinishLaunchingWithOptions:方法中,只需通过点语法self.managedObjectContext就可调用managedObjectContext方法,并返回你需要的上下文。而随着方法的调用,NSManagedObjectModel对象、NSPersistentStoreCoordinator对象以及NSManagedObject对象上下文,三者形成一种多米诺骨牌效应。
最后,applicationWillTerminate:方法调用了saveContext方法,saveContext方法检查在NSManagedObject对象上下文中对象所发生的任何变化,如果有变化就尝试保存。这意味着当程序退出时,持久化存储文件将被来自上下文的变化所更新。一些不同版本的项目模板在applicationDidEnterBackground:方法中调用saveContext方法。
你可能不需要去更改这些方法中的代码,除非你需要使用多个存储区或自定义存储方式。一旦NSManagedObject对象上下文被设置,它就被传递给需要执行存储或提取任务的视图控制器,并将提取的数据显示在视图上,这也是你一般使用Core Data写的最多的一种代码。在第四章——“存储和提取数据基础“,你将开始构建一个使用Core Data提供显示数据的表格视图。
运行程序
为了了解模板项目的功能,构建运行程序。你将发现你可以添加一个Event列表;表视图将显示事件的timeStamp属性。注意你可以从表视图中使用Edit按钮移除这些项目,或者向左滑动手势删除条目。
快速过一遍RootViewController中的代码
为了了解这一切运转的原理,打开RootViewController.m实现文件。TemplateProject使用了NSFetchedResultsController对象来简化对提取结果和表格视图的处理。NSFetchedResultsController对象被惰性创建并只在表格视图数据源方法有需要时才提取数据。
图2.8 运行在模拟器中的Template app
找到fetchedResultsController方法,你会看到在NSFetchRequest对象的配置中,使用了Event实体,并提供了一个NSSortDescriptor对象以让提取结果按timeStamp进行排序。
在表格视图中显示内容的是标准表格视图数据源方法;对于numberOfSections和numberOfRows方法,模板代码只是查询NSFetchedResultsController对象。cellForRowAtIndexPath:方法为了显示数据,使用了一个configureCell:atIndexPath:方法。注意到获得指定的索引持有的NSManagedObject对象是多么简单的一件事。比如显示时间戳的代码,见代码清单2.6。
代码清单2.6 在单元格中显示timeStamp
NSManagedObject *managedObject = [self.fetchedResultsControleler objectAtIndexPath:indexPath]; // 获得指定索引持有的NSManagedObject对象
cell.textLabel.text = [[managedObject valueForKey:@"timeStamp"] description];
NSFetchedResultsController对象返回了指定索引的对象,接着查询返回对象的timeStamp键的描述。
commitEditingStyle:forRowAtIndexPath:方法简单的告诉NSManagedObject对象上下文删除在指定NSIndexPath上的对象,接着命令上下文保存。这将意味着对象从表视图被删除后也会从持久存储区中被删除。
最后,找到insertNewObject方法,当用户尝试加入一个对象到表视图时,该方法将被调用。接着你将看到如下的处理过程:
Ø 获得一个NSManagedObject对象上下文指针;
Ø 决定创建新对象的实体;
Ø 插入一个新的实体对象到NSManagedObject对象上下文;
Ø 对新创建的NSManagedObject对象设置属性值
Ø 命令上下文执行保存。
当上下文执行保存,新的对象将被写到持久存储区中。这是如此简单!
访问.sqlite文件内容
当你在表格视图中插入了一些时间戳记录,为了验证这些数据是否成功保存到sqlite文件。首先在Finder中打开app在模拟器的安装目录:~/Library/Application
Support/iPhone Simulator/[OS version]/Applications/[appGUID]/。进入Documents文件夹,目录内包含了三个文件TemplateProject.sqlite、TemplateProject.sqlite-shm、TemplateProject.sqlite-wal。如上文所述,根据AppDelegate.m文件中的persistentStoreCoordinator方法可知,时间戳记录被存储在TemplateProject.sqlite文件中。为了查看文件中的内容,可以使用Firefox的插件SQLite Manager打开.sqlite文件。但是当你打开.sqlite文件,里面并没有出现你之前添加的时间戳记录!
所以当你在iOS 7中只是拷贝.sqlite文件来实现对数据的备份,很可能将引起数据的丢失和不一致。推荐的做法是使用如下NSPersistentStoreCoordinator类的方法而不是使用文件系统API,实现Core Data存储文件的备份和恢复。
- (NSPersistentStore *)migratePersistentStore:(NSPersistentStore *)store toURL:(NSURL *)URL options:(NSDictionary *)options withType:(NSString *)storeType error:(NSError **)error
你也可以通过代码清单2.7,将-wal日志模式改为回滚日志模式:
代码清单2.7 禁用日志模式
NSDictionary *options = @{NSSQLitePragmasOption:@{@"journal_mode":@"DELETE"}};
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error]) {
// 错误处理
}
当禁用-wal模式之后,再次运行程序,回滚日志模式会强制Core Data执行一个检查点操作,-wal中的数据将会被被合并到主存储文件中,之后TemplateProject.sqlite-wal会被删除。再次使用SQLite Manager打开TemplateProject.sqlite文件,之前插入的时间戳记录终于出现了。如果你既想使用默认的-wal模式,也希望不通过禁用日志模式这种繁琐的操作实现查看.sqlite文件中的内容,此时你可以使用sqlite3的命令行实现强制执行检查点,将-wal文件中的内容合并到.sqlite文件中。代码如下:
苹果公司之所以改变默认的日志模式,是基于性能的考虑,使用-wal模式会有更好的性能。在数据读取的频率比数据写入高的场景中,-wal模式比传统的回滚日志模式慢1%-2%,但是使用-wal模式在绝大多数情况下会更快。WAL支持iOS 4+和OS X 10.7+,所以你需要在iOS 7以下版本中使用WAL,可以使用代码清单2.8所示,开启WAL支持。
代码清单2.8 开启WAL模式
@{ NSSQLitePragmasOption:@ "journal_mode
= WAL" }
总结
Core Data框架基本的5个类::NSPersistentStoreCoordinator、NSManagedObjectContext、NSManagedObjectModel、NSEntityDescription、NSManagedObject。
Ø NSPersistentStoreCoordinator持久化存储协调器(简称协调器):负责从磁盘加载数据和将数据写入磁盘。协调器可以处理多种格式的数据库文件(NSPersistentStore),如二进制文件,XML文件、SQLite文件。你也可以实现自己的数据库文件格式(使用NSAtomicStore和NSIncrementalStore类),理论上你可以实现打开World或Photoshop文件的协调器。
Ø NSEntityDescription实体描述(简称实体):实体可以被看做是NSManagedObject对象的“class”。实体定义了一个NSManagedObject对象所拥有的所有属性(NSAttributeDescription),关系(NSRelationshipDescription),提取属性(NSFetchedPropertyDescription)。
Ø NSManagedObjectContext托管对象上下文(简称上下文):上下文是内存中的一块暂存区域。查询对象(使用NSFetchRequest),创建对象,删除对象等操作都是在上下文中进行。在上下文没有保存之前,对数据的任何修改都只记录在暂存区中,不会影响磁盘上的数据。你可以创建多个上下文,但整个程序只能创建一个NSPersstentStoreCoordinator对象。
Ø NSManagedObject托管对象:Core Data的核心单元。模型对象的数据被持有在NSManagedObject对象中。每一个NSManagedObject对象都对应一个实体(就像每一个对象都有一个类)
Ø NSManagedObjectModel托管对象模型:NSManagedObjectModel通常被定义在一个.mom文件中,文件中保存了所有实体的定义。NSManagedObjectModel and the NS*Description 类完整定义了Core Data模型应该/可以包含的内容。
练习
1. 阅读补充资源:http://www.drdobbs.com/database/understanding-core-data-on-ios/240004648?pgno=1
2. 重新创建一个新的项目,在创建项目时选择不勾选Use Core Data选项。参照TmplateProject项目,在新的项目中实现对Core Data功能的支持。
[1]译注:模型文件TemplateProject.xcdatamodeld实际上是一个包(Package)。包内包含.xccurrentversion和TemplateProject.xcdatamodel。TemplateProject.xcdatamodel也是也一个包,包内包含contents文件。