iOS数据持久化——CoreData

2017-03-15  本文已影响159人  伯牙呀

一、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语句。

二、CoreData结构图

先来张官方的图:

CoreData官方结构图

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


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

<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

那么往数据库中添加数据的时候就可以这样写了:

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>. 查询数据
查询数据需要处理查询结果,要用到两个类:

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入门

上一篇 下一篇

猜你喜欢

热点阅读