# 详解持久化Core Data框架的原理以及使用

语言：zh-CN
原文：[详解持久化Core Data框架的原理以及使用](<https://allluckly.cn/%E6%8C%81%E4%B9%85%E5%8C%96/chijiuhua01>)
中文：[中文 HTML](<https://allluckly.cn/%E6%8C%81%E4%B9%85%E5%8C%96/chijiuhua01>)
English: [English HTML](<https://allluckly.cn/en/journal/2016-01-10-understanding-and-using-core-data-for-persistence/>)
发布于: 2016-01-10T00:00:00+08:00
内容更新于: 2016-01-10T00:00:00+08:00
署名：[Bison](<https://allluckly.cn/about/>)

从托管对象、上下文和持久化协调器的关系讲起，手动搭建 Core Data 环境与实体模型，再通过新增、查询、修改和删除示例说明数据保存的主要流程。

原文署名 Bison，保留原发布时的内容。

**阅读前** 这是 2015–2017 年间的技术记录，本次整理未重新运行验证示例。涉及的 API、依赖和工具版本请结合发表日期阅读。

> "`Care Data`是一个纯粹的面向对象框架，可用于管理实体以及实体之间的关联关系的持久化，也就是我们通常所指的数据持久化。"

## 1.原理部分

`Care Data`是一个纯粹的面向对象框架，可用于管理实体以及实体之间的关联关系的持久化，也就是我们通常所指的数据持久化。  
 `Care Data`底层的持久化存储方式可以是`SQLite`数据库，也可以是`XML`文档，甚至可以直接以内存作为持久化存储设备。  
 `Care Data`的核心概念是实体。实体是由`Care Data`管理的模型对象，它必须是`NSManagedObject`类或其子类的实例。  
 实体与实体之间存在1-1、1-N、N-N、的关联关系，整个应用的所有实体以及实体之间的关联关系被称为托管对象模型`NSManagedObiectModel`。  
 `Care Data`的核心对象是托管对象上下文`NSManagedObjectContext`，所有实体都处于托管对象上下文管理中，  
 `Care Data`应用对实体所做的任何增、删、查、改操作都必须通过托管对象上下文来完成。  
 开发者开发的应用程序需要通过`NSMannagedObjectContext`对实体进行增、删、查、改操作，  
 而`NSMannagedObjectContext`底层与持久化存储协调衔接，持久化存储协调器则负责管理底层的存  
 储形式比如：`SQLite`。  
 `Care Data`应用中的核心`API`有如下几个。

> 托管对象模型`NSManagedObiectModel`：该对象负责管理整个应用的所有实体以及实体之间的关联关系。当开发者使用`Xcode`的图形界面设计了实体与实体的关联关系之后，需要使用该对象来加载、管理应用的托管对象模型。

> 持久化存储协调器`NSPeristentStoreCoordinator`：负责管理底层的存储文件，例如`SQLite`数据库等。

> 托管对象上下文`NSManagedObjectContext`:该对象是`Care Data`的核心对象，应用对实体所做的任何增、删、查、改操作都必须通过该对象来完成。

> 实体描述`NSEntityDescription`：该对象代表了关于某个实体的描述信息，从某种程度来说，该对象相当于实体的抽象。实体描述定义了该实体的名字、实体的实现类，并用一个集合定义了该实体包含的所有属性。

> 抓取请求`NSFetchRequest`：该对象封装了查询实体的请求，包括程序需要查询哪些实体、查询条件、排序规则等。抓取请求定义了本次查询的实体的名字、抓取请求的查询条件，通过`NSPredicate来表示`，并用一个`NSArray`集合定义了所有的排序规则。

#### 熟悉以上几点之后，使用`Core Data`持久化操作的步骤大致如下。

- 创建`NSManagedObiectModel`对象来加载管理应用的托管对象模型。
- 以`NSManagedObiectModel`对象为基础，根据实际需要创建`NSPeristentStoreCoordinator`对象，该对象确定`Core Data`底层数据的存储形式。
- 以`NSManagedObiectModel`对象为基础，创建`NSMannagedObjectContext`，该对象是`Core Data`进行持久化访问的核心对象。
- 对于普通的增、删、查、改操作，需要分别先创建实体、删除实体、修改实体，然后调用`NSMannagedObjectContext`对象的`save:`方法将这些修改保存到底层存储设备。
- 如果要执行查询，则需要先创建` NSFetchRequest  `对象，再调用`NSMannagedObjectContext`的`executeFetchRequest:error`：方法执行查询，该方法返回所有匹配条件的实体组成的`NSArray`。

## 2.手动配置环境

iOS允许在创建项目时勾选“Use Core Data”复选框，通过该方式创建的项目已经完成了所有  
 `Core Data`必须资源的初始化，但此处并不打算使用这种方式来初始化`Core Data`项目，  
 而是选择从一个`Empty Application`开始，手动初始化`Core Data`项目，这样便于大家真  
 正理解`Care Data`项目需要初始化哪些资源。  
 下面我们将开始把这个`Empty Application`项目一步步改造成`Core Data`项目。  
 具体步骤如下：

- 为该项目添加`CoreData.framework`框架。
- 为该项目添加一个实体模型文件。单击Xcode主菜单的”File”—\>”New”—\>”File”菜单项，具体如下图：

![1](<https://allluckly.cn/images/blog/coredata/coredata01.png>)

- 在`AppDelegate`中初始化`Core Data`应用必须的核心` API  `对象：  
   `NSManagedObiectModel`、`NSPeristentStoreCoordinator`、`NSManagedObjectContext`  
   修改应用程序委托类的接口部分，在接口部分定义上面3个核心`API`的属性，并增加一个对  
   ` NSManagedObjectContext  `对象执行存储的方法、一个获取应用`Docouments`目录下的方法。  
   下面是修改后的接口部分代码。

`AppDelegate.h`

```css
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
// 定义Core Data的3个核心API的属性
@property (readonly, strong, nonatomic) NSManagedObjectContext*
managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator*
persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
@end
```

接下来在`AppDelegate.m`中实现部分进行修改，初始化对象，并实现` saveContext  `方法，  
 其中` applicationDocumentsDirectory  `是一个非常简单的方法，用于获取应用`Docouments`目录  
 `AppDelegate.m`

```css
@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    return YES;
}
- (void)applicationWillTerminate:(UIApplication *)application
{
    // 当应用被中断时候，将所有托管上下文中数据保存起来
    [self saveContext];
}

- (void)saveContext
{
    NSError *error = nil;
    // 获取应用的托管对象上下文
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil)
    {
        // 如果托管对象上下文中包含了未保存的修改，执行保存，如果保存失败记录错误信息
        if ([managedObjectContext hasChanges] &&
        ![managedObjectContext save:&error])
        {
            NSLog(@"保存出现错误：%@, %@", error, [error userInfo]);
            abort();
        }
    }
}
// 初始化应用的托管对象上下文。
- (NSManagedObjectContext *)managedObjectContext
{
    // 如果_managedObjectContext已经被初始化过，直接返回该对象	
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
    // 获取持久化存储协调器
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    // 如果持久化存储协调器不为nil
    if (coordinator != nil)
    {
        // 创建NSManagedObjectContext对象
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        // 为NSManagedObjectContext对象设置持久化存储协调器
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}
- (NSManagedObjectModel *)managedObjectModel
{
    // 如果_managedObjectModel已经被初始化过，直接返回该对象
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    // 获取实体模型文件对应的NSURL
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"FKModel"
    withExtension:@"momd"];
    // 加载应用的实体模型文件，并初始化NSManagedObjectModel对象
    _managedObjectModel = [[NSManagedObjectModel alloc]
    initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    // 如果_persistentStoreCoordinator已经被初始化过，直接返回该对象
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    // 获取SQLite数据库文件的存储目录
    NSURL *storeURL = [[self applicationDocumentsDirectory]
    URLByAppendingPathComponent:@"Books.sqlite"];
    NSError *error = nil;
    // 以持久化对象模型为基础，创建NSPersistentStoreCoordinator对象
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
    initWithManagedObjectModel:[self managedObjectModel]];
    // 设置持久化存储协调器底层采用SQLite存储机制，如果设置失败记录错误信息
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
    configuration:nil URL:storeURL options:nil error:&error])
    {
        NSLog(@"设置持久化存储失败：%@, %@", error, [error userInfo]);
        abort();
    }
    return _persistentStoreCoordinator;
}
// 获取应用的Documents目录
- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
    inDomains:NSUserDomainMask] lastObject];
}
```

到此`Core Data`所依赖的环境基本上已经搭建完毕，应用执行增、删、查、改操作直接调用  
 ` managedObjectContext  `方法即可操作。

## 3.设计实体模型

Xcode中找到我们创建的`Core Data`文件，打开实体模型开始编辑，具体如下图：

![2](<https://allluckly.cn/images/blog/coredata/coredata02.png>)

下面我们添加一个简单的实体，点击上图走下角的`Add Entity`，系统将会在`ENTITIES`列表下添加一个实体，  
 将该实体重命名为项目所需的名字，此处为`Bison`

#### PS长按`Add Entity`将会显示`Add Entity`、`Add Fetch Request`、`Add Configuration`列表，可供选择添加实体、抓取请求、配置。

选中`Bison`实体，通过点击上图右下角的`Add Attribute`按钮，系统将会为该`Bison`实体添加一个属性，  
 将该属性重命名所需的名字，此处重命名`name`，并为该属性选择类型，在此选`String`类型,注意：此处命名  
 首字母不能大写哦。

#### PS长按`Add Attribute`将会显示`Add Attribute`、`Add Relationship`、`Add Configuration`、Add Fetch Property列表，可供选择添加属性、关联关系、抓取属性，也可以通过 ` +  ` 、` —  `来添加与删除。

重复上面的操作增加一个`birthDay`的属性，改属性为`date`类型。实体设计完后，如下图：  
 ![3](<https://allluckly.cn/images/blog/coredata/coredata03.jpg>)   
 然后单击Xcode主菜单的`Editor`–\>`Create NSManagedObject Subclass`菜单项，  
 如果系统包含俩个以上的实体，Xcode将会弹出一个对话框让选择腰围哪些实体生成`NSManagedObject`  
 的子类，然后弹出对话框让选择`NSManagedObject`的子类的存储路径。选完之后`Create`按钮即可。  
 经过上面的操作，为实体模型添加一个简单的`Bison`实体，该实体包含俩个属性，单不包含任何关联关系。

## 4.`Core Data`数据的增删查改

获取托管对象上下文`NSManagedObjectContext`之后，接下来即可通过该对象来执行增、删、查、改操作。

- 添加实体  
   添加实体的步骤如下：  
   调用`NSEntityDescription`的`insertNewObjectForEntityForName:inManagedObjectContext:`  
   静态方法添加新实体。该方法的第1个参数为实体名，第2个参数为`NSManagedObjectContext`对象。  
   为新实体设置属性。调用` NSManagedObjectContext  `对象的`save:`方法执行保存。如下代码片段：

```css
// 控制Core Data在托管对象上下文中创建一个新实体
Bison* bison = [NSEntityDescription
insertNewObjectForEntityForName:@"Bison"
inManagedObjectContext:self.appDelegate.managedObjectContext];
// 为新实体设置属性
bison.name = @"linbin";
bison.birth_Day = [NSDate date];
// 定义一个NSError对象，用于接受错误信息
NSError *error;
// 设置完实体属性之后，调用托管对象上下文的`save:`方法将实体写入数据库，如果保存成功
if ([self.appDelegate.managedObjectContext save:&error])
{
    [[[UIActionSheet alloc] initWithTitle:@"保存成功" delegate:nil
    cancelButtonTitle:@"确定" destructiveButtonTitle:nil
    otherButtonTitles: nil] showInView:self.view];
}else{
    NSLog(@"保存FKEvent实体出错: %@,%@" , error ,[error userInfo]);
}
```

- 删除实体  
   删除实体的步骤如下:  
   首先获取要删除的实体，然后调用`NSManagedObjectContext`对象的`deleteObject:`  
   方法删除实体。最后调用` NSManagedObjectContext  `对象的`save:`方法执行保存。  
   如下代码：

```css
// 获取将要删除的实体
Bison* deleteEvent = .....;
// 从托管对象上下文中删除指定对象
[self.appDelegate.managedObjectContext deleteObject:deleteEvent];
NSError *error;
// 保存删除操作，如果出现错误，显示错误信息
if (![self.appDelegate.managedObjectContext save:&error])
{
    NSLog(@"删除FKEvent实体出错:%@,%@",error,[error userInfo]);
}
```

- 修改实体 修改实体的步骤如下: 首先获取要修改的实体，必须处于`NSManagedObjectContext`管理下的实体；然后修改实体的属性；再调用` NSManagedObjectContext  `对象的`save:`方法执行保存。如下代码：

```css
// 获取将要修改的实体
Bison* updateEvent = .....;
//修改实体的属性
.....
//定义一个NSError对象，用于接收错误信息
NSError *error;
// 保存修改操作，如果出现错误，显示错误信息
if (![self.appDelegate.managedObjectContext save:&error])
{
    NSLog(@"删除FKEvent实体出错:%@,%@",error,[error userInfo]);
}
```

- 查询实体  
   查询实体的步骤如下:  
   首先创建`NSFetchRequest`对象。然后通过`NSEntityDescription`对象设置  
   ` NSFetchRequest  `对象将要抓取的实体。如果需要对抓取结果进行筛选，则需要  
   通过`NSPredicate`对象设置筛选条件。如果需要对结果进行排序，还需要为  
   `NSFetchRequest`添加多个`NSSortDescriptor`对象。再调用  
   `NSManagedObjectContext`对象的`executeFetchRequest:error:`  
   方法执行查询，该查询方法将会返回所有符合条件的实体组成的`NSArray`集合。  
   如下代码：

```css
// 创建抓取数据的请求对象
NSFetchRequest *request = [[NSFetchRequest alloc] init];
// 设置要抓取哪种类型的实体
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Bison"
inManagedObjectContext:self.appDelegate.managedObjectContext];
// 设置抓取实体
[request setEntity:entity];
NSError *error = nil;
// 执行抓取数据的请求，返回符合条件的数据
eventArray = [[self.appDelegate.managedObjectContext
executeFetchRequest:request error:&error] mutableCopy];
```

基本的方法到此就结束了，具体的详情可以下载Demo查看

> [下载地址](<https://github.com/AllLuckly/CoreDataTest/tree/master>)

好文推荐：[仿window阿里旺旺登陆界面,打印机吐纸动画效果](<https://allluckly.cn/sdk/aliww>)

原文地址：[https://allluckly.cn/](<https://allluckly.cn/>)

版权归©Bison所有 如需转载请保留原文超链接地址！否则后果自负！

