一、CoreData简介
CoreData是iOS5之后新出来的的一个框架,是对SQLite进行一层封装升级后的一种数据持久化方式。
它提供了对象<-->关系映射
((ORM))的功能,即能够将OC对象转化为数据,存储到SQLite数据库文件中,同时也能将数据库中的数据还原成OC对象。
简单地用下图描述下它的作用:
左边是关系模型,即数据库,数据库里面有张person表,person表里面有id、name、age三个字段,而且有2条记录;
右边是对象模型,可以看到,有2个OC对象;
利用Core Data框架,我们就可以轻松地将数据库里面的2条记录转换成2个OC对象,也可以轻松地将2个OC对象保存到数据库中,变成2条表记录,而且不用写一条SQL语句。
好处:
能够合理管理内存,避免使用SQL语句的麻烦,高效。
相较于SQLite,在此数据操作期间,我们不需要编写任何SQL语句。妈妈再也不用担心我的SQL语句"缺斤少两"了。存储类型:
数据最终的存储类型可以是:SQLite数据库,XML,二进制,内存里,或自定义数据类型。
在Mac OS X 10.5及以后的版本中,开发者也可以通过继承NSPersistentStore类以创建自定义的存储格式。
二、CoreData结构图
先来张官方的图:
PersistentObjectStore:存储持久对象的数据库(例如SQLite,注意CoreData也支持其他类型的数据存储,例如xml、二进制数据等)。
ManagedObjectModel:对象模型,对应Xcode中创建的模型文件。
PersistentStoreCoordinator:对象模型和实体类之间的转换协调器,用于管理不同存储对象的上下文。
ManagedObjectContext:对象管理上下文,负责实体对象和数据库之间的交互。
说了这么多,可能你还是懵逼的,下面是形象的图:
最底层的就是 PersistentObjectStore,也就是我们实际存储数据的结构;
图中的模型就是 ManagedObjectModel,就是数据转化为对象的模板。
以SQLite数据库为例:
- 读取数据库的数据时,数据库数据先进入数据解析器,根据对应的模板,生成对应的关联对象。
- 向数据库插入数据时,对象管理器先根据实体描述创建一个空对象,对该对象进行初始化,然后经过数据解析器,根据对应的模板,转化为数据库的数据,插入数据库中。
- 更新数据库数据时,对象管理器需要先读取数据库的数据,拿到相互关联的对象,对该对象进行修改,修改的数据通过数据解析器,转化为数据库的更新数据,对数据库更新。
这些还是要在使用中进行加深理解。
三、CoreData使用
1. 添加框架
添加框架 CoreData.framework
导入头文件 #import
2. 数据模型
在CoreData中,需要进行映射的对象称为实体(entity),而且需要使用CoreData的模型文件来描述app中的所有实体和实体属性。这里以Person(人)和Card(身份证)2个实体为例子,先看看实体属性和实体之间的关联关系:
首先创建一个数据模型,即 ManagedObjectModel
:
<1>. 选择模板
<2>. 添加实体
<3>. 添加Person的2个基本属性
<4>. 添加Card的1个基本属性
<5>. 建立Card和Person的关联关系
在图Person关联Card
中的
3. 对象模型
<1>. 了解NSManagedObject
- 通过CoreData从数据库取出的对象,默认情况下都是NSManagedObject对象。
- NSManagedObject的工作模式有点类似于NSDictionary对象,通过键-值对来存取所有的实体属性。
- setValue:forKey:存储属性值(属性名为key)
- valueForKey:获取属性值(属性名为key)
<2>. 创建NSManagedObject的子类
默认情况下,利用CoreData取出的实体都是NSManagedObject类型的,能够利用键-值对来存取数据。但是一般情况下,实体在存取数据的基础上,有时还需要添加一些业务方法来完成一些其他任务,那么就必须创建NSManagedObject的子类。
选择数据模型:
选择需要创建子类的实体:
创建完毕后,多了2个子类:
文件内容展示:
Person.h
#import
#import
@class Card;
@interface Person : NSManagedObject
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * age;
@property (nonatomic, retain) Card *card;
@end
Person.m
#import "Person.h"
@implementation Person
@dynamic name;
@dynamic age;
@dynamic card;
@end
Card.h
#import
#import
@class Person;
@interface Card : NSManagedObject
@property (nonatomic, retain) NSString * no;
@property (nonatomic, retain) Person *person;
@end
Card.m
#import "Card.h"
#import "Person.h"
@implementation Card
@dynamic no;
@dynamic person;
@end
所有的实体类型都继承于NSManagedObject,每个NSManagedObject对象对应着数据库中一条记录。
集合属性(例如数组)会自动生成访问此属性的分类方法。
使用
@dynamic
代表具体属性实现,具体实现细节不需要开发人员关心。
那么往数据库中添加数据的时候就可以这样写了:
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
person.name = @"Jay";
person.age = [NSNumber numberWithInt:18];
Card *card = [NSEntityDescription insertNewObjectForEntityForName:@”Card" inManagedObjectContext:context];
card.no = @”12345678910";
person.card = card;
// 最后调用[context save&error];保存数据
这里只看数据的赋值(添加数据),至于存储下面会继续讲。
4. 代码实现
<1>. 创建对象管理上下文
创建对象管理上下文ManagedObjectContext可以细分为:
- 加载模型文件
- 指定数据存储路径
- 创建对应数据类型的存储
- 创建管理对象上下方并指定存储
- (NSManagedObjectContext *)createDbContext {
// 打开模型文件,参数为nil则打开包中所有模型文件并合并成一个
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
// 传入模型对象,初始化数据解析器NSPersistentStoreCoordinator
NSPersistentStoreCoordinator *storeCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// 创建数据库保存路径
NSString *dir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES).lastObject;
NSString *path = [dir stringByAppendingPathComponent:@"myDatabase.db"];
NSURL *url = [NSURL fileURLWithPath:path];
// 添加持久化存储库,这里使用SQLite作为存储库,添加SQLite持久存储到解析器
NSError *error;
[storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error];
NSManagedObjectContext *context = nil;
if(!error) {
// 创建对象管理上下文,并设置数据解析器
context = [[NSManagedObjectContext alloc] init];
context.persistentStoreCoordinator = storeCoordinator;
NSLog(@"数据库打开成功!");
} else {
NSLog(@"数据库打开失败!错误:%@",error.localizedDescription);
}
return context;
}
<2>. 插入数据(添加数据)
- (void)addClassTest {
// 传入上下文,创建一个Person实体对象
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
// 设置Person的简单属性
[person setValue:@"Jay" forKey:@"name"];
[person setValue:[NSNumber numberWithInt:18] forKey:@"age"];
// 传入上下文,创建一个Card实体对象
NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];
[card setValue:@"12345678910" forKey:@"no"];
// 设置Person和Card之间的关联关系
[person setValue:card forKey:@"card"];
// 利用上下文对象,将数据同步到持久化存储库
NSError *error = nil;
BOOL success = [context save:&error];
// 保存上下文,这里需要注意,增、删、改操作完最后必须调用管理对象上下文的保存方法,否则操作不会执行。
// 如果是想做更新操作:只要在更改了实体对象的属性后调用 [context save:&error],就能将更改的数据同步到数据库。
if (!success) {
NSLog(@"添加过程中发生错误,错误信息:%@!",error.localizedDescription);
}
}
<3>. 删除数据
- (void)removeObject {
// 传入需要删除的实体对象
[context deleteObject:managedObject];
// 将结果同步到数据库
NSError *error;
BOOL success = [context save:&error];
if (!success) {
NSLog(@"删除过程中发生错误,错误信息:%@!",error.localizedDescription);
}
}
<4>. 查询数据
查询数据需要处理查询结果,要用到两个类:
- NSFetchRequest:获取数据的请求
- NSPredicate:请求的谓词,也就是获取数据的要求
1、查询一个对象只有唯一一个关联对象的情况
例如查找用户名为“Jay”的微博(一个微博只能属于一个用户),通过keypath查询:
- (NSArray *)getStatusByUserName:(NSString *)name {
// 初始化查询请求
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
// 初始化谓词,设置获取数据的条件
request.predicate = [NSPredicate predicateWithFormat:@"user.name=%@",name];
// 执行对象管理上下文的查询方法
NSError *error = nil;
NSArray *array = [self.context executeFetchRequest:request error:&error];
if (error) {
NSLog(@"查询错误,错误信息:%@!",error.localizedDescription);
}
return array;
}
2、查询一个对象有多个关联对象的情况
例如查找发送微博内容中包含“Watch”并且用户昵称为“小杰”的用户(一个用户有多条微博)
- (NSArray *)getUsersByStatusText:(NSString *)text screenName:(NSString *)screenName{
// 初始化查询请求
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
// 设置查询条件
request.predicate = [NSPredicate predicateWithFormat:@"text LIKE '*Watch*'",text];
// 获取查询结果
NSError *error = nil;
NSArray *statuses = [self.context executeFetchRequest:request error:&error];
if (error) {
NSLog(@"查询错误,错误信息:%@!",error.localizedDescription);
}
// 下面是用谓词对上面的结果进行过滤
NSPredicate *userPredicate = [NSPredicate predicateWithFormat:@"user.screenName=%@",screenName];
// 对查询结果再进行过滤
NSArray *users = [statuses filteredArrayUsingPredicate:userPredicate];
return users;
}
另外,查询一个对象,设置排序
// 初始化一个查询请求
NSFetchRequest *request = [[NSFetchRequest alloc] init];
// 设置要查询的实体
request.entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];
// 设置排序(按照age降序)
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:sort];
// 设置条件过滤(搜索name中包含字符串"Itcast-1"的记录,注意:设置条件过滤时,数据库SQL语句中的%要用*来代替,所以%Itcast-1%应该写成*Itcast-1*)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@", @"*Itcast-1*"];
request.predicate = predicate;
// 执行请求
NSError *error = nil;
NSArray *objs = [context executeFetchRequest:request error:&error];
if (error) {
[NSException raise:@"查询错误" format:@"%@", [error localizedDescription]];
}
// 遍历数据
for (NSManagedObject *obj in objs) {
NSLog(@"name=%@", [obj valueForKey:@"name"]
}
注:CoreData不会根据实体中的关联关系立即获取相应的关联对象,比如通过CoreData取出Person实体时,并不会立即查询相关联的Card实体;当应用真的需要使用Card时,才会再次查询数据库,加载Card实体的信息。这个就是CoreData的延迟加载机制。
四、CoreData调试
打开CoreData的SQL语句输出开关:
- 打开Product,点击EditScheme
- Run -> Arguments
- 点击Arguments,在ArgumentsPassed On Launch中添加2项
1>-com.apple.CoreData.SQLDebug
2>1
然后在运行程序过程中,如果操作了数据库,就会将SQL语句打印在输出面板。
注意:如果模型发生了变化,此时可以重新生成实体类文件,但是所生成的数据库并不会自动更新,这时需要考虑重新生成数据库并迁移原有的数据。
Core Data入门