iOS数据持久化——CoreData

一、CoreData简介

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

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

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


iOS数据持久化——CoreData_第1张图片
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结构图

先来张官方的图:

iOS数据持久化——CoreData_第2张图片
CoreData官方结构图
  • PersistentObjectStore:存储持久对象的数据库(例如SQLite,注意CoreData也支持其他类型的数据存储,例如xml、二进制数据等)。

  • ManagedObjectModel:对象模型,对应Xcode中创建的模型文件。

  • PersistentStoreCoordinator:对象模型和实体类之间的转换协调器,用于管理不同存储对象的上下文。

  • ManagedObjectContext:对象管理上下文,负责实体对象和数据库之间的交互。

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


iOS数据持久化——CoreData_第3张图片
CoreData结构图

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

以SQLite数据库为例:

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

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

三、CoreData使用

1. 添加框架

添加框架 CoreData.framework
导入头文件 #import

iOS数据持久化——CoreData_第4张图片
CoreData.framework

2. 数据模型

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

iOS数据持久化——CoreData_第5张图片
属性和实体的关联关系

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

<1>. 选择模板

iOS数据持久化——CoreData_第6张图片
Data Model
iOS数据持久化——CoreData_第7张图片
Data Model 命名

<2>. 添加实体

iOS数据持久化——CoreData_第8张图片
Add Entity

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

iOS数据持久化——CoreData_第9张图片
添加Person的基本属性

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

iOS数据持久化——CoreData_第10张图片
添加Card的基本属性

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

iOS数据持久化——CoreData_第11张图片
Card关联Person
iOS数据持久化——CoreData_第12张图片
Person关联Card

在图Person关联Card中的

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

iOS数据持久化——CoreData_第13张图片
3. 对象模型

<1>. 了解NSManagedObject

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

<2>. 创建NSManagedObject的子类

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

iOS数据持久化——CoreData_第16张图片
NSManagedObjectsubclass

选择数据模型:


iOS数据持久化——CoreData_第17张图片

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


iOS数据持久化——CoreData_第18张图片

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


iOS数据持久化——CoreData_第19张图片
生成的类

文件内容展示:

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语句输出开关:

  1. 打开Product,点击EditScheme
  2. Run -> Arguments
  3. 点击Arguments,在ArgumentsPassed On Launch中添加2项
    1> -com.apple.CoreData.SQLDebug
    2> 1
    iOS数据持久化——CoreData_第20张图片
iOS数据持久化——CoreData_第21张图片
CoreData调试设置

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

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

Core Data入门

你可能感兴趣的:(iOS数据持久化——CoreData)