iOS数据持久化CoreData

释放双眼,带上耳机,听听看~!

一、CoreData简介

CoreData是iOS5之后新出来的的一个框架,是对SQLite进行一层封装升级后的一种数据持久化方式。

它提供了对象<-->关系映射((ORM))的功能,即能够将OC对象转化为数据,存储到SQLite数据库文件中,同时也能将数据库中的数据还原成OC对象。

简单地用下图描述下它的作用:

 

CoreData作用简介

左边是关系模型,即数据库,数据库里面有张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结构图

先来张官方的图:

CoreData官方结构图
  • PersistentObjectStore:存储持久对象的数据库(例如SQLite,注意CoreData也支持其他类型的数据存储,例如xml、二进制数据等)。
  • ManagedObjectModel:对象模型,对应Xcode中创建的模型文件。
  • PersistentStoreCoordinator:对象模型和实体类之间的转换协调器,用于管理不同存储对象的上下文。
  • ManagedObjectContext:对象管理上下文,负责实体对象和数据库之间的交互。

说了这么多,可能你还是懵逼的,下面是形象的图:

 

CoreData结构图

最底层的就是 PersistentObjectStore,也就是我们实际存储数据的结构;
图中的模型就是 ManagedObjectModel,就是数据转化为对象的模板。

以SQLite数据库为例:

  • 读取数据库的数据时,数据库数据先进入数据解析器,根据对应的模板,生成对应的关联对象。
  • 向数据库插入数据时,对象管理器先根据实体描述创建一个空对象,对该对象进行初始化,然后经过数据解析器,根据对应的模板,转化为数据库的数据,插入数据库中。
  • 更新数据库数据时,对象管理器需要先读取数据库的数据,拿到相互关联的对象,对该对象进行修改,修改的数据通过数据解析器,转化为数据库的更新数据,对数据库更新。

这些还是要在使用中进行加深理解。

三、CoreData使用

1. 添加框架

添加框架 CoreData.framework
导入头文件 #import<CoreData/CoreData.h>

CoreData.framework
2. 数据模型

在CoreData中,需要进行映射的对象称为实体(entity),而且需要使用CoreData的模型文件来描述app中的所有实体和实体属性。这里以Person(人)和Card(身份证)2个实体为例子,先看看实体属性和实体之间的关联关系:

属性和实体的关联关系

首先创建一个数据模型,即 ManagedObjectModel

<1>. 选择模板

Data Model
Data Model 命名

<2>. 添加实体

Add Entity

<3>. 添加Person的2个基本属性

添加Person的基本属性

<4>. 添加Card的1个基本属性

添加Card的基本属性

<5>. 建立Card和Person的关联关系

Card关联Person
Person关联Card

在图Person关联Card中的

表示Card中有个Person类型的person属性,目的就是建立Card跟Person之间的一对一关联关系(建议补上这一项),在Person中加上Inverse属性后,你会发现Card中Inverse属性也自动补上了。

3. 对象模型

<1>. 了解NSManagedObject

  • 通过CoreData从数据库取出的对象,默认情况下都是NSManagedObject对象。
  • NSManagedObject的工作模式有点类似于NSDictionary对象,通过键-值对来存取所有的实体属性。
    • setValue:forKey:存储属性值(属性名为key)
    • valueForKey:获取属性值(属性名为key)

<2>. 创建NSManagedObject的子类

默认情况下,利用CoreData取出的实体都是NSManagedObject类型的,能够利用键-值对来存取数据。但是一般情况下,实体在存取数据的基础上,有时还需要添加一些业务方法来完成一些其他任务,那么就必须创建NSManagedObject的子类。

NSManagedObjectsubclass

选择数据模型:

 

选择需要创建子类的实体:

 

创建完毕后,多了2个子类:

 

生成的类

文件内容展示:

Person.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@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 <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@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语句输出开关:

  1. 打开Product,点击EditScheme
  2. Run -> Arguments
  3. 点击Arguments,在ArgumentsPassed On Launch中添加2项
    1> -com.apple.CoreData.SQLDebug
    2> 1

CoreData调试设置

然后在运行程序过程中,如果操作了数据库,就会将SQL语句打印在输出面板。

注意:如果模型发生了变化,此时可以重新生成实体类文件,但是所生成的数据库并不会自动更新,这时需要考虑重新生成数据库并迁移原有的数据。

Core Data入门

人已赞赏
iOS文章

iOS 通过图片NSData数据来获取图片格式

2020-1-3 11:11:04

iOS文章

IOS UIControl控件

2020-1-3 11:56:09

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
有新消息 消息中心
搜索