Core data 是 Cocoa 中处理数据,绑定数据的关键特性,其重要性不言而喻,但也比较复杂。Core Data相关的类比较多,初学者往往不太容易弄懂。我近期花了一周多时间,才基本入门,今天将一些要点整理出来分享:
一、Core Data 框架的概述
Core Data 是 Cocoa 里面一套非常受欢迎的框架,从 Mac OS X 10.4 提供以来,在 10.5 中引入了完善的schema 迁移机制,再到 iPhone OS 3.0 时被引入 Cocoa Touch,这套完善的框架都被认为是管理大量结构化数据所首选的 Cocoa 框架,尤其是因为使用 Core Data 能大大减少需要手工编写的代码量,就使它更受开发者欢迎了。
不过最近却出现了一些不同的声音,先是传出消息说 Aperture 3.0 抛弃了 Core Data ,改为直接操作 SQLite数据库 (大家联想到 Apple Mail 3.0 也是直接用 SQLite,没有用 Core Data),但因为都是 Apple 内部的决定,大家只能凭空猜测理由;接下来,NetNewsWire 的开发者 Brent Simmons 也说在 NetNewsWire for iPhone 里从 Core Data 转向用 FMDB 来操作 SQLite 数据库 (FMDB 是 Gus Mueller 编写的一层很薄的 SQLite 在 Objective-C 下的封装),Brent 给的理由就很充分了:他要做的很多操作都是对数据批量进行的,其实不需要把所有数据都保存在内存里遍历执行,那样更慢,直接交给数据库,往往一条语句就搞定了,简洁而且快速。
然后 Jonathan “Wolf” Rentzsch 也对此深表赞同 ,并推荐 Aaron Hillegass 的 BNRPersistence 框架,这个框架用Tokyo Cabinet 提供了一个类似 Core Data 接口的数据持久化方案,最大的优点是比 Core Data 快得多,根据Aaron 自己的测试,常见的操作都要快 10 - 20 倍。其实快这么多也可以理解,毕竟 BNRPersistence 要比 Core Data 轻量得多,支持的功能也少很多,加上 Tokyo Cabinet 这样的 Key-value 数据库在处理适合它的操作时,多数要比 SQLite 这样的关系型数据库要快。
所以突然 Core Data 就有点被墙倒众人推的意思,好像以前大家都知道它不好用,但都不好意思说,直到突然有经验足够丰富的开发者开头,就一涌而上开始骂了。我个人的观感是 Core Data 作为官方方案,给开发提供的许多便利还是不可小视的,但考虑学起来确实也不容易 (所以才有人专门写本书讲 Core Data ),所以新上手的Cocoa 程序员不妨先考虑一下 BNRPersistence, FMDB 这样的方案。
那是否 Core Data 就该被抛弃呢?目前的争议其实有点像 Web 开发里到底该不该用 ORM , 区别是大多数Web 开发者因为历史原因,对直接进行数据库操作有偏好 (或者说,本能地反感 ORM),而多数 Cocoa 开发者则坚定支持用对象操作数据,所以长远来看,数据持久化方案在 Cocoa (Touch) 开发里少不了,唯一的疑问是 Apple会不会继续改进 Core Data 的性能和接口,以拉近与第三方方案的差距,只要 Apple 还在不断改进,Core Data 就有学习的必要。
我个人对 Core Data 的怨念主要是在要写的“胶水代码”上:有的时候为了方便与界面的 bindings 操作,不得不给 Model 写大量重复的胶水代码,所以如果第三方的方案如果在这方面有简化,我很乐意改用。
二、Core Data与SQLite数据库
并非严格的说, CoreData是对SQLite数据库的一个封装。
在苹果iPhone或iPad上,你可以存放数据在以下三个地方
其一是:一个或多个文件
其二是:应用的属性列表(NSUserDefaults)
其三是:内置的数据库(SQLite)
通常SQLite数据库操作的基本流程是, 创建数据库, 再通过定义一些字段来定义表格结构, 可以利用sql语句向表格中插入记录, 删除记录, 修改记录, 表格之间也可以建立联系。
这个过程出现了, 表格的结构(schema), 所有表格的结构和相互联系构成整个数据库的模型, 数据库存放的方式(可以是文件或者在内存), 数据库操作, sql语句(主要是查询), 表格里面的记录,下面将上面说的文字,跟CoreData的类作个对应:
SQLite数据库操作 | VS | CoreData的类 |
表格结构 | --> | NSEntityDescription |
数据库中所有表格和他们的联系 | --> | NSManagedObjectModel |
数据库存放方式 | --> | NSPersistentStoreCoordin |
数据库操作 | --> | NSManagedObjectContext |
查询语句 | --> | NSFetchRequest |
表格的记录 | --> | NSManagedObject |
可能上面的对应关系并非十分严格, 但确实可以帮助理解.
下面再看看CoreData的类
首先是NSEntityDescription和NSManagedObjectModel这两个非常重要的类
NSEntityDescription用来定义表格结构,所以你就可以理解NSManagedObjectModel中的setEntities:(NSArray *)entities函数大概有什么用了,通常, 定义model,是用文件CoreData.xcdatamodel, 可以图形化的操作. 这类似用nib来创建界面。
例如:建个工程, 使用core
用命令行sqlite3 CoreData.sqlite 进入
>.tables
ZEVENT
可以看到有表格ZEVENT, 对应的CoreData.xcdatamodel文件有名字叫Event的Entity
>.schema ZEVENT
CREATE TABLE ZEVENT ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZTIMESTAMP TIMESTAMP );
对应的Event中有属性timeStamp, 可以看到, 相应的ZEVENT表格中有字段TIMESTAMP
> select * from ZEVENT
1|1|1|306295807.974966
2|1|1|306295810.981875
3|1|1|306295811.982537
这表格有三个记录, 可以用来初始化三个NSManagedObject, 修改了NSManagedObject, save之后也修改了表格记录。
你可以在CoreData.xcdatamodel添加新的entity, 之后用sqlit3命令来查看数据库的变化。
三、深入认识Core Data 框架
下面先给出一张类关系图,让我们对它有个总体的认识。
在上图中,我们可以看到有五个相关模块:
Managed Object Context 参与对数据对象进行各种操作的全过程,并监测数据对象的变化,以提供对undo/redo 的支持及更新绑定到数据的 UI。使用Core Data的框架,大多数的功能都可以自动实现,因为我们有managed object context(管理对象的上下文,有时直接叫"Context")。managed object context就像是一个关卡,通过它可以访问框架底层的对象——这些对象的集合我们称之为"persistence stack"(数据持久栈)。 managed object context作为程序中对象和外部的数据存储的中转站。栈的底部是persistence object stores(持久化数据存储)
Persistent Store Coordinator 相当于数据文件管理器,处理底层的对数据文件的读取与写入。一般我们无需与它打交道。之前提到过,程序中的对象和外部存储的数据通过Core Data框架中的一系列对象进行协调,这一系列的对象总的被称为持久存储栈(Persistence stack)。在栈顶是被管理对象上下文(Managed object context),而栈底是持久化对象存储层(Persistence object store)。在它们之间就是持久化存储助理。
这些模块是怎样运作的呢?
1、应用程序先创建或读取模型文件(后缀为xcdatamodeld)生成 NSManagedObjectModel 对象。Document应用程序是一般是通过 NSDocument 或其子类 NSPersistentDocument)从模型文件(后缀为xcdatamodeld)读取。
2、然后生成 NSManagedObjectContext 和 NSPersistentStoreCoordin
3、NSPersistentStoreCoordin
4、NSManagedObjectContext 参与对数据进行各种操作的整个过程,它持有 Managed Object。我们通过它来监测 Managed Object。监测数据对象有两个作用:支持 undo/redo 以及数据绑定。这个类是最常被用到的。
5、Array Controller, Object Controller, Tree Controller 这些控制器一般与NSManagedObjectContext 关联,因此我们可以通过它们在 nib 中可视化地操作数据对象。
四、Model class
模型有点像数据库的表结构,里面包含 Entry, 实体又包含三种 Property:Attribute(属性),RelationShip(关系), Fetched Property(读取属性)。Model class 的名字多以 "Description" 结尾。我们可以看出:模型就是描述数据类型以及其关系的。
主要的 Model class 有:
Model Classes | ||
Managed Object Model | NSManagedObjectModel | 数据模型 |
Entity | NSEntityDescription | 抽象数据类型,相当于数据库中的表 |
Property | NSPropertyDescription | Entity 特性,相当于数据库表中的一列 |
| NSAttributeDescription | 基本数值型属性(如Int16, BOOL, Date等类型的属性) |
| NSRelationshipDescriptio | 属性之间的关系 |
| NSFetchedPropertyDescrip | 查询属性(相当于数据库中的查询语句) |
1)Entity - NSEntityDescription
Entity 相当于数据库中的一个表,它描述一种抽象数据类型,其对应的类为 NSManagedObject 或其子类。
NSEntityDescription 常用方法:
+insertNewObjectForEntity
-managedObjectClassName //返回映射到 Entity 的 NSManagedObject 类名
-attributesByName //以名字为 key, 返回 Entity 中对应的 Attributes
-relationshipsByName //以名字为 key, 返回 Entity 中对应的 Relationships
2)Property – NSPropertyDescription
Property 为 Entity 的特性,它相当于数据库表中的一列,或者 XML 文件中的 value-key 对中的 key。它可以描述实体数据属性(Attribute),Entity之间的关系(RelationShip),或查询属性(Fetched Property)。
Attribute 存储基本数据,如 NSString, NSNumber or NSDate 等。它可以有默认值,也可以使用正则表达式或其他条件对其值进行限定。一个属性可以是 optional 的。
Relationship 描述 Entity,Property 之间的关系,可以是一对一,也可以是一对多的关系。
Fetched Property 根据查询谓词返回指定 Entity 的符合条件的数据对象。
上面说的比较抽象,举个例子来说,见图:
我们通常使用 KVC 机制来访问 Property。下面来看代码:
[代码]c#/cpp/oc代码:
1 | NSManagedObjectContext * context = [[NSApp delegate ] managedObjectContext]; |
2 | NSManagedObject * author = nil; |
3 | |
4 | author = [NSEntityDescription insertNewObjectForEntityForName: @"Author" inManagedObjectContext: context]; |
5 | [author setValue: @"nemo@pixar.com" forKey: @"email" ]; |
6 |
7 | NSLog ( @"The Author's email is: %@" , [author valueForKey: @"email" ]); |
在上面代码中,我们先取得 NSManagedObjectContext,然后调用 NSEntityDescription 的方法,以 Author 为实体模型,生成对应的 NSManagedObject 对象,插入 NSManagedObjectContext 中,然后给这个对象设置特性 email的值。
> Managed Object – NSManagedObject
Managed Object 表示数据文件中的一条记录,每一个 Managed Object 在内存中对应 Entity 的一个数据表示。Managed Object 的成员为 Entity 的 Property 所描述。
比如在上面的代码,author 这个 NSManagedObject,对应名为 Author 的 Entity。
每一个 Managed Object 都有一个全局 ID(类型为:NSManagedObjectID)。Managed Object 会附加到一个Managed Object Context,我们可以通过这个全局 ID 在 Managed Object Context 查询对应的 Managed Object。
NSManagedObject 常用方法 | |
-entity | 获取其 Entity |
-objectID | 获取其 Managed Object ID |
-valueForKey: | 获取指定 Property 的值 |
-setValue: forKey: | 设定指定 Property 的值 |
> Managed Object Context – NSManagedObjectContext
Managed Object Context 的作用相当重要,对数据对象进行的操作都与它有关。当创建一个数据对象并插入Managed Object Context 中,Managed Object Context 就开始跟踪这个数据对象的一切变动,并在合适的时候提供对 undo/redo 的支持,或调用 Persistent Store Coordinato 将变化保存到数据文件中去。
通常我们将 controller 类(如:NSArrayController,NSTreeController)或其子类与 Managed Object Context 绑定,(如何绑定?后有介绍)这样就方便我们动态地生成,获取数据对象等。
NSManagedObjectContext 常用方法 | |
-save: | 将数据对象保存到数据文件 |
-objectWithID: | 查询指定 Managed Object ID 的数据对象 |
-deleteObject: | 将一个数据对象标记为删除,但是要等到 Context 提交更改时才真正删除数据对象 |
-undo | 回滚最后一步操作,这是对undo/redo 的支持 |
-lock | 加锁,常用于多线程以及创建事务。同类接口还有:-unlock and -tryLock |
-rollback | 还原数据文件内容 |
-reset | 清除缓存的 Managed Objects。只应当在添加或删除 Persistent Stores 时使用 |
-undoManager | 返回当前 Context 所使用的 NSUndoManager |
-assignObject: toPersistantStore: | 由于 Context 可以管理从不同数据文件而来的数据对象,这个接口的作用就是指定数据对象的存储数据文件(通过指定 PersistantStore 实现) |
-executeFetchRequest: error: | 执行 Fetch Request 并返回所有匹配的数据对象 |
> Persistent Store Coordinator - NSPersistentStoreCoordin
使用 Core Data document 类型的应用程序,通常会从磁盘上的数据文中中读取或存储数据,这写底层的读写就由 Persistent Store Coordinator 来处理。一般我们无需与它直接打交道来读写文件,Managed Object Context 在背后已经为我们调用 Persistent Store Coordinator 做了这部分工作。
NSPersistentStoreCoordin | |
-addPersistentStoreForURL | 装载数据存储,对应的卸载数据存储的接口为 -removePersistentStore:error: |
-migratePersistentStore:toURL:options:withType:error: | 迁移数据存储,效果与 "save as"相似,但是操作成功后,迁移前的数据存储不可再使用 |
-managedObjectIDForURIRep | 返回给定 URL所指示的数据存储的 object id,如果找不到匹配的数据存储则返回 nil |
-persistentStoreForURL: | 返回指定路径的 Persistent Store |
-URLForPersistentStore: | 返回指定 Persistent Store 的存储路径 |
> Persistent Document - NSPersistentDocument
NSPersistentDocument 是 NSDocument 的子类。 multi-document Core Data 应用程序使用它来简化对Core Data 的操作。通常使用 NSPersistentDocument 的默认实现就足够了,它从 Info.plist 中读取Document types 信息来决定数据的存储格式(xml,sqlite, binary)。
NSPersistentDocument 常用方法 | |
-managedObjectContext | 返回文档的 Managed Object Context,在多文档应用程序中,每个文档都有自己的 Context。 |
-managedObjectModel | 返回文档的 Managed Object Model |
Fetch Requests 相当于一个查询语句,你必须指定要查询的 Entity。我们通过 Fetch Requests 向Managed Object Context 查询符合条件的数据对象,以 NSArray 形式返回查询结果,如果我们没有设置任何查询条件,则返回该 Entity 的所有数据对象。我们可以使用谓词来设置查询条件,通常会将常用的 Fetch Requests 保存到 dictionary 以重复利用。
NSFetchRequest 常用方法 | |
-setEntity: | 设置你要查询的数据对象的类型(Entity) |
-setPredicate: | 设置查询条件 |
-setFetchLimit: | 设置最大查询对象数目 |
-setSortDescriptors: | 设置查询结果的排序方法 |
-setAffectedStores: | 设置可以在哪些数据存储中查询 |
[代码]c#/cpp/oc代码:
01 | NSManagedObjectContext * context = [[NSApp delegate ] managedObjectContext]; |
02 | NSManagedObjectModel * model = [[NSApp delegate ] managedObjectModel]; |
03 | NSDictionary * entities = [model entitiesByName]; |
04 | NSEntityDescription * entity = [entities valueForKey: @"Post" ]; |
05 | |
06 | NSPredicate * predicate; |
07 | predicate = [NSPredicate predicateWithFormat: @"creationDate > %@" , date]; |
08 | |
09 | NSSortDescriptor * sort = [[NSortDescriptor alloc] initWithKey: @"title" ]; |
10 | NSArray * sortDescriptors = [NSArray arrayWithObject: sort]; |
11 | |
12 | NSFetchRequest * fetch = [[NSFetchRequest alloc] init]; |
13 | [fetch setEntity: entity]; //设置你要查询的数据对象的类型(Entity) |
14 | [fetch setPredicate: predicate]; //设置查询条件 |
15 | [fetch setSortDescriptors: sortDescriptors]; // 设置查询结果的排序方法 |
16 | |
17 | NSArray * results = [context executeFetchRequest:fetch error:nil]; |
18 | [sort release]; |
19 | [fetch release]; |
在上面代码中,我们查询在指定日期之后创建的 post,并将查询结果按照 title 排序返回。
参考资料:
Core Data Reference API listing for the Core Data classes
http://developer.apple.com/documentation/Cocoa/Reference/CoreData_ObjC/index.html
NSPredicate Reference API listing for NSPredicate
http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSPredicate.html
原贴地址:http://www.devdiv.com/iPhone开发学习笔记——Core_Data_框架及运作过程和设计的类-weblog-228548-49362.html