Core Data系列文章(一)Core Data基础

iOS开发数据库SQLite的使用介绍了iOS中使用SQLite对数据进行持久化存储,实际上是对数据库直接进行操作,而苹果专门有一套API来间接的对数据进行持久化存储,而且主要针对用户创建的对象 — Core Data

Core Data有很多强大的功能,使用图形化界面来创建对象。可以使用NSPredicate对数据进行筛选,使用NSSortDescriptor对数据进行排序。还可以通过获取属性与请求模板的设置筛选数据。也可以建立对象与对象之间的关联关系,一对一,一对多,多对多种情况。对于每个对象的属性也可以指定约束和验证的规则。而且苹果还对Core Data的性能做了优化,如添加对查询数据的缓存,关联对象不会立刻访问达到节省内存的目的等。Core Data 还提供了Mapping Model来解决数据库版本升级的问题。

本篇主要介绍Core Data的架构与Core Data使用的基础配置。

Core Data架构

很多人刚刚接触Core Data 时只看一些模板代码与API并不知道Core Data设计架构是什么样的,导致不了解Core Data内部是如何工作以至于学习起来总感觉生涩难懂,了解Core Data的设计架构对学习和理解Core Data是有很大的帮助。首先我们先看看Core Data中的对象以及这些对象间是如何工作的:

数据存储

数据存储是通过持久存储协调器将用户创建的对象写入磁盘中,或者从磁盘中读取用户创建的对象。存储的文件可以是一个数据库文件,可以是一个二进制文件在初始化持久协调器时可以指定存储的类型。

注意: 不要尝试修改存储的文件,如果对使用Core Data存储的文件进行修改,将改变文件结构导致使用Core Data读取文件时出错

持久化存储协调器

持久化存储协调器的是NSPersistentStoreCoordinator类的实例,只是扮演托管对象上下文和数据存储之间的中间人角色,当托管对象上下文进行数据请求时通过持久化存储协调器从存储文件的位置获取数据,而托管上下文存储数据是也是通过是持久化存储协调器进行数据存储。

注意:NSPersistentStoreCoordinator类不是线程安全的类当多个线程需要对数据进行操作时需要给NSPersistentStoreCoordinator类的实例加锁或者在每个线程中重新实例化一个NSPersistentStoreCoordinator类。关于锁的使用请参考我之前写的一篇文章Objective-C中的同步线程的锁

托管对象模型

托管对象模型定义了应用中使用的数据模型的结构,托管对象是NSManagedObjectModel类的实例对象,托管对象模型由一组实体组成每一组实体都表示一类数据结构,可以定义每个模型的结构与各个模型之间的关系。持久化存储协调器根据托管对象模型中定义的数据结构与每个实体之间的关系来创建托管对象,并将这些托管对象存到磁盘中。

托管对象上下文

托管对象上下文是NSManagedObjectContext类的实例,托管对象上下文主要提供一个访问托管对象的接口,管理所有的托管对象。可以在托管对象上下文中对托管对象进行操作,包括添加对象,删除对象,修改对象,查找对象。一般从磁盘中获取数据时都要创建一个请求,指定托管对象上下文然后设置请求的条件来访问数据。


Core Data使用的基础配置

下面通过创建一个带有Core Data模板的项目来介绍Core Data使用时各项参数配置。

新建一个Xcode项目选择Master-Detail Application

点击Next并勾选Use Core Data

此时可以先运行查看一下效果,每次添加都会增加一行显示当前时间的信息,当完全退出应用时再次打开这些信息还存在说明了已经对数据持久化存储了。

AppDelegate里可以看到Core Data相关对象初始化的过程。首先是托管对象模型NSManagedObjectModel的创建

1
2
3
4
5
6
7
8
9
- (NSManagedObjectModel *)managedObjectModel {
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"LSYBaseData" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}

在应用的项目文件中包含后缀为.xcdatamodeld的文件这个文件就是托管对象模型,在后台Xcode会将这个文件编译为momd类型的文件,momd文件是部署到设备上的文件。

然后是持久化存储协调器NSPersistentStoreCoordinator类的实例创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it.
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}

// Create the coordinator and store

_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"LSYBaseData.sqlite"];
NSError *error = nil;
NSString *failureReason = @"There was an error creating or loading the application's saved data.";
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}

return _persistentStoreCoordinator;
}

通过传入一个托管对象模型来初始化NSPersistentStoreCoordinator对象,因为NSPersistentStoreCoordinator对象是托管对象模型、托管对象上下文、数据存储的中间人,后面可以看到创建托管对象上下文也需要用到NSPersistentStoreCoordinator对象。
持久化存储协调器指定了存储类型为数据库文件所以通过创建一个.sqlite文件的路径来存储数据。
之后的代码是将数据存储添加到协调器。第一个参数NSSQLiteStoreType说明应该是数据库存储,当然也可以指定其他的存储参数。第二个参数configuration,通过传入configuration的名称将不同的托管对象模型存储到不同的数据文件中,默认将托管对象模型中的所有实体对象都存到同一个文件中。第三个参数options通过传入一个字典来添加对数据存储的附加配置,后面添加模型映射时会添加此配置。此时不需要所以传nil。

最后一段代码是创建一个托管对象的上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}

通过一个上面创建的持久化存储协调器来配置一个托管对象上下文。

现在Core Data 基本对象都已经初始化那么在MasterViewController.m文件中查看Core Data是如果对数据进行操作的。

首先Core Data想要获取数据必须构建一个请求。然后请求结果的控制器根据构建的请求和托管对象上下文来获取数据。请求结果控制器通过与托管对象上下文绑定,每当上下文数据发生变化时,请求结果控制器都会调用相应的代理方法。

下面查看初始化请求结果控制器的相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // 初始化请求
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:self.managedObjectContext]; //获取实体描述
[fetchRequest setEntity:entity];

[fetchRequest setFetchBatchSize:20]; //设置获取数量

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO];

[fetchRequest setSortDescriptors:@[sortDescriptor]]; //设置排序条件可以多个条件

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"]; // 初始化请求结果控制器
aFetchedResultsController.delegate = self; // 设置代理
self.fetchedResultsController = aFetchedResultsController;

NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}

return _fetchedResultsController;
}

下面是获取请求结果的相关代理方法:

1
2
3
4
5
6
7
8
9
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath;

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;

- (nullable NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName;

然后是添加数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)insertNewObject:(id)sender {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext]; //获取托管对象上下文
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity]; //获取实体名称
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; //添加托管对象

[newManagedObject setValue:[NSDate date] forKey:@"timeStamp"]; //设置托管对象timeStamp属性值

// Save the context.
NSError *error = nil;
if (![context save:&error]) //保存数据
{
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}

托管对象更改属性名是通过KVC方式进行修改,如果一旦属性名写错的话就会导致程序崩溃,后面的文章会通过实例化NSManagedObject的子类提供gettersetter方法来修改属性。

删除托管对象是通过托管对象上下文对象提供的方法来删除的,具体实现在UITableView的代理中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

NSError *error = nil;
if (![context save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}

结束

本篇主要介绍了Core Data架构与使用时的基本配置以及对数据的基本操作。后面一系列文章将介绍Core Data众多的进阶使用。