在项目中创建和使用Core Data

来源:互联网 发布:中国武器知乎 编辑:程序博客网 时间:2024/06/04 18:12

Core Data包含的内容非常丰富,这篇文章是关于Core Data的比较基础和常用的内容;包括Core Data的组成,和如何在应用中构建和使用Core Data。主要包括以下内容:

  • 什么是Core Data
  • Core Data 栈
  • 建立一个Core Data驱动的应用
  • 使用Core Data操作数据

1、什么是Core Data

Core Data是一种基于数据模型的数据管理解决方案,是苹果提供的关系-对象映射的原生解决方案。在iOS系统架构中位于核心服务层,使用接口是一组Objective-C的类;它是设计用来与MVC设计模式协同工作的,部分工作可以用图形化的方式进行编辑;

不同于关系型数据库,Core Data并不在意值,它关注的是对象;从Core Data中取出信息时,它会创建并返回一个装有受控对象的数组。Core Data会自动对结果数据的值进行包装,封装成应用中使用的模型对象,然后将这些对象当作获取操作的结果返回。


2、Core Data栈

Core Data栈可由下面的Core Data架构示意图展示,下图将Core Data栈中的各种参与者以及它们之间的互动展示出来,描述了Core Data的工作过程。Core Data栈是由以下内容构成的:

  • 受控对象模型
  • 持久化存储调度器
  • 持久化存储和存储文件
  • 受控对象上下文

示意图

2.1、受控对象模型

受控对象模型(managed object model),简称对象模型,负责定义应用中的数据结构。对象模型存储在可视化文件中,扩展名为.xcdatamodeld。它可以用一个图形化界面工具来编辑,即Core Data数据模型编辑器(见下图)。

Core Date GUI

在上图中显示了Core Data模型编辑器的工作区界面,图中对象模型定义了一个Person实体,实体包含了几个简单的属性。

在一个受控对象模型中,每个对象(Objective-C类)都被称为一个实体(entity)。每个实体都有自己单独的一个列表,其中列出了属性(attribute)、关系(relationship)、和衍生属性(fetched property)。
可以将属性(attribute)看作是自己定义对象时使用的实例属性,如上图中的id、name等。关系定义了单个实体彼此之间的联系。
派生属性也表示了对象模型中实体间的联系。关系和派生属性的区别在于,关系是双向的(双方对象都知道关系的存在),而派生属性只是单向的。

在图形工作区创建完对象模型之后,就可以用Xcode来自动生成受控对象类了。在Xcode的菜单中新建文件,在新建的对话框中选择Core Data文件类型,选择NSManagedObject作为基类并单击Next(见下图)。之后,可以在对话框中选择所有需要创建对象类的实体,之后,就可以生成这些子类了,生成的类包括选中的实体类(如Person类),还有实体和Core Data属性相关的分类。

新建受控对象

2.2、持久化存储调度器

Core Data栈中,其次重要的元素就是持久化存储调度器。通过受控对象模型,可以创建一个持久化存储调度器。这个对象模型定义的实体和关系会受该调度器的管理。

持久化存储调度器在Core Data中基本上是一个自动的过程。除了一开始的创建过程之外,在应用的整个生命周期中,并不需要操作这个调度器。大多数使用core data的iOS应用,都是围绕单个数据库设计的。

但是如果一个应用拥有多个存储文件,调度器的用处就会凸显出来,它会管理底层的存储,而给开发者提供一个单一的受控对象上下文,使得使用起来更方便。

2.3、持久化存储和存储文件

在使用Core Data时,需要在文件系统中创建一个新的数据库文件的持久化存储。持久化存储其实就是对实际的数据库文件的一种Objective-C的表示方式。不用自己去创建新的持久化存储,只要确定了存储类型、配置、URL和选项之后,就可以将新的持久化存储直接添加到一个已有的调度器中。

下面的代码创建了一个新的持久化存储:

    //获取沙盒document路径    NSString *documentPath =  [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];    //为数据库文件创建一个URL    NSURL *storeURL = [[NSURL URLWithString:documentPath] URLByAppendingPathComponent:@"DemoCoreData.sqlite"];   //创建持久化存储    NSError *error = nil;    NSPersistentStore *store;   //coordinator为NSPersistentStoreCoordinator的对象,属于持久化存储调度器    store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];    //如果创建失败,则进行错误处理    if (!store) {        //错误处理    }

由于数据库是一个.sqlite文件,所以在添加存储时,除了传入存储的URL之外,还要将存储的类型设置为NSSQLiteStoreType。

这里添加的存储是一个SQLite数据库。Core Data支持3种类型的存储:NSSQLiteStoreType、NSBinaryStorageType、NSInMemoryStoreType。这3种类型的性能从速度上来说都差不多,但从数据模型中保留下来的信息不一样。SQLite类型的存储只保留了部分关系图,在处理大型数据集时更有效率,是iOS开发中最经常使用的。

2.4、受控对象上下文

有了受控对象模型,也以此初始化了持久化存储调度器。读取数据库文件的持久化存储也添加到了这个调度器中。还剩下操作和使用数据,这部分由受控对象上下文负责。

Core Data获取数据时返回的是对象,当对象被创建出来之后,它们就存在于一个受控对象上下文中。受控对象上下文的工作就是管理Core Data创建并返回的对象。

在创建了受控对象上下文后,需要为这个上下文设置持久化存储调度器,这样调度器就可以访问实体了。

以下代码是通过受控对象上下文从数据库中取得所有的人员(Person对象),可以用来说明受控对象上下文在core data栈中的角色:

    //将获取请求的实体设置为人员对象    NSEntityDescription *issueEntity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:objectContext];    //创建一个新的获取请求    NSFetchRequest *request = [[NSFetchRequest alloc] init];    [request setEntity:issueEntity];    //获取结果    NSError *error = nil;    NSArray *fetchResults = [objectContext executeFetchRequest:request error:&error];    //遍历所有结果    for (Person *person in fetchResults) {        NSLog(@"%@", person.name);    }

受控对象上下文创建的对象是受它自身管理的。在这个例子中,对象上下文会监视它返回的Person对象所受到的任何改变。如果修改了这些对象的属性,对象上下文会自动纪录下这些变化。当修改完毕后,只要调用上下文的保存操作,就可以让上下文将所有修改传递给调度器,然后保存到持久化存储中。

3、创建一个Core Data应用

在新建工程时如果选中了“Use Core Data”复选框,Xcode就会自动在工程中引入Core Data框架,并在应用代理类中加上创建和管理Core Data栈的方法。

如果新建工程是未选中“Use Core Data”复选框,而之后又希望使用Core Data作为存储机制,那就需要手动在工程中创建Core Data栈,创建需要以下几个步骤(对应Core Data栈的四个组成部分):

  • 创建受控对象模型,在Xcode Core Data模型编辑器中进行。
  • 创建持久化存储调度器,初始化时用受控对象模型做参数。
  • 为调度器添加一个持久化对象存储。
  • 创建新的受控对象上下文并设置存储调度器。

3.1、创建受控对象模型

要新建一个受控对象模型,需要在工程中添加一个数据模型编辑器文件(见下图)。

新建对象模型

数据模型文件可以用Core Data模型编辑器来编辑和修改。在Core Data模型编辑器中定义好实体和关系之后,同时还需要生成NSmanagedObject的子类。

3.2、创建新的持久化存储调度器

在模型编辑器创建好模型后,下一步就是用这个模型来生成持久化存储调度器。下面的代码演示了如何新建一个调度器:

//获得对象数据模型的引用    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];//用这个文件的URL新建一个NSManagedObjectModel对象    objectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];//用这个objectModel对象新建NSPersistentStoreCoordinator对象    coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:objectModel];

初始化了持久化存储调度器之后,剩下的事给他加上持久化存储,在把这个调度器设置到一个受控对象上下文中。

3.3、添加新的持久化存储

当获得了存储调度器之后,还需要向其中加入新的存储,添加时要指定存储的类型、配置、文件URL和选项。新建存储器需要以下代码:

    //获取沙盒document路径    NSString *documentPath =  [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];    //为数据库文件创建一个URL    NSURL *storeURL = [[NSURL URLWithString:documentPath] URLByAppendingPathComponent:@"DemoCoreData.sqlite"];   //创建持久化存储    NSError *error = nil;    NSPersistentStore *store;   //coordinator为NSPersistentStoreCoordinator的对象,属于持久化存储调度器    store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];

3.4、创建新的受控对象上下文

在创建新的受控对象上下文时,需要注意的是,必须在对应的分发队列中创建和访问这个上下文中的对象。受控对象上下文可以使用下列3种并发类型来进行初始化,分别是NSConfinementConcurrencyType、NSMainQueueConcurrencyType、NSPrivateQueueConcurrencyType三种。

NSConfinementConcurrencyType是传统的初始化类型,在哪里初始化,只能在对应的线程上使用;
NSMainQueueConcurrencyType和NSPrivateQueueConcurrencyType初始化受控对象上下文时,会在一个GCD分发队列之内进行操作。当使用基于队列的上下文时,该上下文创建的对象只在创建它的队列内有效;在这种情况下,如果在一个事毕处理代码块中或者是一个单独的分发队列中创建或者使用Core Data相关的对象,都是无效的。

受控对象上下文提供了两种便捷方法,可以确保操作始终都在正确的队列中完成。这些方法就是performBlock和performBlockAndWait。

使用GCD和代码块处理过程更简单高效和方便控制,因此推荐根据并发类型选用一个队列。以下代码建立了一个新的受控对象上下文:

    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];    //执行代码块中设置上下文    [moc performBlockAndWait:^{        //设置持久化存储,coordinator为上一节创建的持久化存储调度器        [moc setPersistentStoreCoordinator:coordinator];    }];    self.managedObjectContext = moc;

以下代码使用的并发类型是主队列类型,在第一行分配了新的NSManagedObjectContext对象,指定并发类型为NSMainQueueConcurrencyType。在第四行用performBlockAndWait方法在受控对象上下文之上同步执行了一些操作,这些操作都会在正确的队列中完成。

4、使用Core Data

现在已经在应用程序中建立起了一套Core Data栈,可以开始使用它操作其中的数据了。在通过受控对象上下文与Core Data持久化存储进行互动的过程中,一般的操作包括以下4种类型之一:

  • 添加新对象;
  • 获取对象并修改数据;
  • 删除对象;
  • 撤销、重做、回滚和复位。

4.1、添加新对象

我们和持久化存储的所有互动都是转交给受控对象上下文完成的,在进行所有这些互动时,可以把受控对象上下文想像成是持久化存储的一个“工作副本”。对受控对象上下文所做的修改,直到使用正确的保存操作提交之后,才会对数据产生影响。

以下代码展示了如何新建10个新的Person对象:

for (int i = 0; i < 10; i++) {        //向上下文中插入并返回一个新的Person对象        Person *newPerson = (Person *)[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.managedObjectContext];        //设置新Person的名称        [newPerson setName:[NSString stringWithFormat:@"Person#%i", i]];    }    //调用保存上下文,将修改提交到持久化存储中    NSError *error = nil;    if (![self.managedObjectContext save:&error]) {        NSLog(@"保存出错");    }

首先代码第三行在上下文中创建了一个新的对象,创建方法是:在上下文中通过一个实体的名称插入一个新对象。由于每个实体都有自己的类名,所以这个方法返回的值是一个通用的id对象。在使用这个对象前,必须对返回值做类型转换;之后,给这个对象设置了一个名称。

所有对象新建处理完成以后,我们保存了上下文,没有必要在每次创建一个对象之后都保存上下文。因为受控对象上下文会纪录在两次保存动作之间发生的所有修改,所以没有必要每次改动之后都调用保存函数。实际上,那样做可能会导致性能严重下降。可以把保存操作当做是使用受控对象上下文的最后一步。应该先执行完当前任务需要对数据进行的一切修改,然后在最后一步保存上下文。

4.2、获取并修改对象

对于一个经过获取操作返回的对象,其受到的任何修改也都会被受控对象上下文记录下来。这意味着对这些对象所做的任何修改,在对创建这些对象的上下文调用保存操作时,才会被提交到持久化存储中。

在下面的代码中,我们在受控对象上下文执行了同样的获取操作。这里我们为获取操作定义了一个谓词,用来将检索参数限定为具有特别名称的对象。一旦得到了结果,就会修改该对象的属性,并保存上下文来提交这些修改。

//将获取请求的实体设置为Person对象    NSEntityDescription *personEntity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:managedObjectContext];    //创建一个新的请求    NSFetchRequest *request = [NSFetchRequest new];    [request setEntity:personEntity];    //为请求设置一个谓词来限制请求的结果    //只需要具有指定名称的期刊    NSPredicate *query = [NSPredicate predicateWithFormat:@"name = %@", name];    [request setPredicate:query];    //获取结果    NSError *error = nil;    NSArray *fetchResults = [objectContext executeFetchRequest:request error:&error];    //如果得到了结果,就修改其属性    if ([fetchResults count] > 0) {        Person *person = [fetchResults objectAtIndex:0];        person.address = @"ShangHai";        person.name = @"coredata";    }    //调用保存上下文,将修改提交到持久化存储中    NSError *error = nil;    if (![self.managedObjectContext save:&error]) {        NSLog(@"保存出错");    }

4.3、删除对象

删除一个现存对象与添加新对象很相似。在上下文上调用删除操作时,将要删除的对象作为参数传递过去,然后调用上下文的保存操作就可以将修改提交到持久化存储。

下面的代码展示了如何从持久化存储中删除一个对象:

    Person *person = [self personWithName:name];    if (person) {        [self.managedObjectContext deleteObject:person];    }    if ([self.managedObjectContext hasChanges]) {        //保存改动        NSError *error = nil;        if (![self.managedObjectContext save:&error]) {            NSLog(@"保存出错");        }    }

在第六行保存上下文前,这里检查了受控对象上下文是否有需要保存的修改。如果personWithName方法返回了nil值,那么删除对象的操作就不会执行。这样,受控对象上下文将不会有需要保存的新信息。为了优化程序的性能,最好只在需要时才执行保存操作。

4.4、撤销、重做、回滚和复位

使用Core Data的一个好处就是,在应用的整个生命周期中,受控对象上下文会自动纪录撤销和重做状态。

这项功能是透过NSUndoManager来达成的。NSUndoManager是iOS用来跟踪数据变化的手段,用以管理撤销操作。通常情况下,如果要在应用中支持撤销操作,就必须创建自己的撤销操作管理器(undo manager),并在事件发生时予以记录。不过,如果用了Core Data,在用户事件发生时,受控对象上下文自动在与其关联的撤销操作管理器中添加撤销/重做的快照。

添加撤销操作管理器

默认情况下iOS上的Core Data没有自带撤销操作管理器,这主要是为了提高iOS的性能。因为iOS上,不是所有使用Core Data的应用程序都会用到撤销操作,所以管理器默认被设置为nil。(在Mac OS X上,Core Data会自动为新建的受控对象上下文分配一个撤销操作管理器。)

下面的代码展示了如何给一个受控对象上下文分配一个撤销操作管理器:

    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];    //设置执行代码块中设置上下文    [moc performBlockAndWait:^{        //添加撤销操作管理器        NSUndoManager *undoManager = [NSUndoManager new];        [moc setUndoManager:undoManager];        //设置持久化存储        [moc setPersistentStoreCoordinator:coordinator];    }];    self.managedObjectContext = moc;

默认情况下,一个受控对象上下文的撤销操作管理器会设置为在收到用户事件时记录撤销操作快照。用户事件在这里指的是由用户触发的控件事件、触摸事件或者晃动设备的事件。

关于撤销操作管理器,需要注意的一点是,它只有在用户事件发生时才会记录快照。如果没有经过用户操作,而是通过程序添加了对象的话,撤销操作管理器是不会记录这些改动的。撤销操作管理器是用来撤销用户的动作,而不是撤销开发者对受控对象上下文的全部操作的。

下表概括了在受控对象上下文上调用撤销、重做、回滚和复位方法的效果。

方 法 名 描述 undo 撤销自上次用户控件事件发生以来,对数据所做的任何修改 redo 重做对上下文所做的最后一组修改。只有在之前调用过undo方法时,这个方法才能重做被撤销的修改 rollback 将受控对象上下文的所有修改回滚,回到最后一次提交的状态。这个操作同时也会清空纪录在撤销操作缓存中的所有修改 reset 清空纪录在撤销操作缓存中的所有修改

5、总结

这边文章整理记录了Core Data的基础知识,包含了Core Data栈的介绍,以及如何在一个项目中使用Core Data作为存储和操作数据的机制;可以在新建工程时选择使用Core Data框架,也可以自己在应用中建立一套Core Data栈。

0 1
原创粉丝点击