认识CoreData - 基础使用

该文章属于<简书 — 刘小壮>原创,转载请注明:

<简书 — 刘小壮> http://www.jianshu.com/p/0ddfa35c7898git


第一篇文章中并无讲CoreData的具体用法,只是对CoreData作了一个详细的介绍,算是一个开始和总结吧。github

这篇文章中会主要讲CoreData的基础使用,以及在使用中须要注意的一些细节。由于文章中会插入代码和图片,内容可能会比较多,比较考验各位耐心。正则表达式

文章中若有疏漏或错误,还请各位及时提出,谢谢!`sql


占位图

建立自带CoreData的工程

在新建一个项目时,能够勾选Use Core Data选项,这样建立出来的工程系统会默认生成一些CoreData的代码以及一个.xcdatamodeld后缀的模型文件,模型文件默认以工程名开头。这些代码在AppDelegate类中,也就是表明能够在全局使用AppDelegate.h文件中声明的CoreData方法和属性。数据库

系统默认生成的代码是很是简单的,只是生成了基础的托管对象模型、托管对象上下文、持久化存储调度器,以及MOCsave方法。可是这些代码已经能够完成基础的CoreData操做了。数组

系统生成代码

这部分代码不该该放在AppDelegate中,尤为对于大型项目来讲,更应该把这部分代码单独抽离出去,放在专门的类或模块来管理CoreData相关的逻辑。因此我通常不会经过这种方式建立CoreData,我通常都是新建一个**“干净”**的项目,而后本身往里面添加,这样对于CoreData的完整使用流程掌握的也比较牢固。缓存


CoreData模型文件的建立

构建模型文件

使用CoreData的第一步是建立后缀为.xcdatamodeld的模型文件,使用快捷键Command + N,选择Core Data -> Data Model -> Next,完成模型文件的建立。安全

建立完成后能够看到模型文件左侧列表,有三个选项EntitiesFetch RequestsConfigurations,分别对应着实体、请求模板、配置信息。多线程

模型文件

添加实体

如今能够经过长按左侧列表下方的Add Entity按钮,会弹出Add EntityAdd Fetch RequestAdd Configuration选项,能够添加实体、请求模板、配置信息。这里先选择Add Entity来添加一个实体,命名为Person并发

添加Person实体后,会发现一个实体对应着三部份内容,AttributesRelationshipsFetched Properties,分别对应着属性、关联关系、获取操做。

空实体

如今对Person实体添加两个属性,添加age属性并设置typeInteger 16,添加name属性并设置typeString

添加属性

实体属性类型

在模型文件的实体中,参数类型和平时建立继承自NSObject的模型类大致相似,可是仍是有一些关于类型的说明,下面简单的列举了一下。

  • Undefined: 默认值,参与编译会报错
  • Integer 16: 整数,表示范围 -32768 ~ 32767
  • Integer 32: 整数,表示范围 -2147483648 ~ 2147483647
  • Integer 64: 整数,表示范围 –9223372036854775808 ~ 9223372036854775807
  • Float: 小数,经过MAXFLOAT宏定义来看,最大值用科学计数法表示是 0x1.fffffep+127f
  • Double: 小数,小数位比Float更精确,表示范围更大
  • String: 字符串,用NSString表示
  • Boolean: 布尔值,用NSNumber表示
  • Date: 时间,用NSDate表示
  • Binary Data: 二进制,用NSData表示
  • Transformable: OC对象,用id表示。能够在建立托管对象类文件后,手动改成对应的OC类名。使用的前提是,这个OC对象必须遵照并实现NSCoding协议

添加实体关联关系

建立两个实体DepartmentEmployee,而且在这两个实体中分别添加一些属性,下面将会根据这两个实体来添加关联关系。

建立实体

Employee实体添加关系,在Relationships的位置点击加号,添加一个关联关系。添加关系的名称设为department,类型设置为DepartmentInverse设置为employee(后面会讲解这个inverse的做用)。

添加Relationships

选择Department实体,点击Relationships位置的加号,添加关联关系。(须要注意的是,inverse须要设置好Relationships以后才能设置)

Department实体添加Relationships的操做和Employee都同样,区别在于用红圈标出的Type,这里设置的To Many一对多的关系。这里默认是To One一对一,上面的Employee就是一对一的关系。也就符合一个Department能够有多个Employee,而Employee只能有一个Department的状况,这也是符合常理的。

添加Relationships

Relationships相似于SQLite的外键,定义了在同一个模型中,实体与实体之间的关系。能够定义为对一关系或对多关系,也能够定义单向或双向的关系,根据需求来肯定。若是是对多的关系,默认是使用NSSet集合来存储模型。

Inverse是两个实体在Relationships中设置关联关系后,经过设置inverse为对应的实体,这样能够从一个实体找到另外一个实体,使两个实体具备双向的关联关系。

Fetched Properties

在实体最下面,有一个Fetched Properties选项,这个选项用的很少,这里就不细讲了。

Fetched Properties用于定义查询操做,和NSFetchRequest功能相同。定义fetchedProperty对象后,能够经过NSManagedObjectModel类的fetchRequestFromTemplateWithName:substitutionVariables:方法或其余相关方法获取这个fetchedProperty对象。

fetched Property

获取这个对象后,系统会默认将这个对象缓存到一个字典中,缓存以后也能够经过fetchedProperty字典获取fetchedProperty对象。

Data Model Inspector

选中一个实体后,右侧的侧边栏(Data Model Inspector)还有不少选项,这些选项能够对属性进行配置。根据不一样的属性类型,侧边栏的显示也不太同样,下面是一个String类型的属性。

Data Model Inspector

属性设置
  • default Value: 设置默认值,除了二进制不能设置,其余类型几乎都能设置。

  • optional: 在使用时是否可选,也能够理解为若是设置为NO,只要向MOC进行save操做,这个属性是否必须有值。不然MOC进行操做时会失败并返回一个error,该选项默认为YES

  • transient: 设置当前属性是否只存在于内存,不被持久化到本地,若是设置为YES,这个属性就不参与持久化操做,属性的其余操做没有区别。transient很是适合存储一些在内存中缓存的数据,例如存储临时数据,这些数据每次都是不一样的,并且不须要进行本地持久化,因此能够声明为transient的属性。

  • indexed: 设置当前属性是不是索引。添加索引后能够有效的提高检索操做的速度。可是对于删除这样的操做,删除索引后其余地方还须要作出相应的变化,因此速度会比较慢。

  • Validation: 经过Validation能够设置Max ValueMin Value,经过这两个条件来约定数据,对数据的存储进行一个验证。数值类型都有相同的约定方式,而字符串则是约定长度,date是约定时间。

  • Reg. Ex.(Regular Expression): 能够设置正则表达式,用来验证和控制数据,不对数据自身产生影响。(只能应用于String类型)

  • Allows External Storage: 当存储二进制文件时,若是遇到比较大的文件,是否存储在存储区以外。若是选择YES,存储文件大小超过1MB的文件,都会存储在存储区以外。不然大型文件存储在存储区内,会形成SQLite进行表操做时,效率受到影响。

Relationships设置
  • delete rule: 定义关联属性的删除规则。在当前对象和其余对象有关联关系时,当前对象被删除后与之关联对象的反应。这个参数有四个枚举值,代码对应着模型文件的相同选项。

NSNoActionDeleteRule 删除后没有任何操做,也不会将关联对象的关联属性指向nil。删除后使用关联对象的关联属性,可能会致使其余问题。 NSNullifyDeleteRule 删除后会将关联对象的关联属性指向nil,这是默认值。 NSCascadeDeleteRule 删除当前对象后,会将与之关联的对象也一并删除。 NSDenyDeleteRule 在删除当前对象时,若是当前对象还指向其余关联对象,则当前对象不能被删除。

  • Type: 主要有两种类型,To OneTo Many,表示当前关系是一对多仍是一对一。
实体
  • Parent Entity: 能够在实体中建立继承关系,在一个实体的菜单栏中经过Parent Entity能够设置父实体,这样就存在了实体的继承关系,最后建立出来的托管模型类也是具备继承关系的。注意继承关系中属性名不要相同。 使用了这样的继承关系后,系统会将子类继承父类的数据,存在父类的表中,全部继承自同一父类的子类都会将父类部分存放在父类的表中。这样可能会致使父类的表中数据量过多,形成性能问题。

Fetch Requests

在模型文件中Entities下面有一个Fetch Requests,这个也是配置请求对象的。可是这个使用起来更加直观,能够很容易的完成一些简单的请求配置。相对于上面讲到的Fetched Properties,这个仍是更方便使用一些。

Fetch Requests

上面是对Employee实体的height属性配置的Fetch Request,这里配置的height小于2米。配置以后能够经过NSManagedObjectModel类的fetchRequestTemplateForName:方法获取这个请求对象,参数是这个请求配置的名称,也就是EmployeeFR

Editor Style

这是我认为CoreData最大的优点之一,可视化的模型文件结构。能够很清楚的看到实体和属性的关系,以及实体之间的对应关系。

Editor Style

一个.xcdatamodeld模型文件的展现风格有两种,一种是列表的形式(Table),另外一种是图表的形式展现(Graph)。

图表看起来更加直观,而图表在操做上也有一些比Table更方便的地方。例如在Table的状态下添加两个实体的关联关系,若是只作一次关联操做,默认是单向的关系。而在Graph的状态下,按住Control对两个图表进行连线,两个实体的结果就是双向关联的关系。

手动建立实体

假设不使用.xcdatamodeld模型文件,全都是纯代码,怎么在项目里建立实体啊?这样的话就须要经过代码建立实体描述、关联描述等信息,而后设置给NSManagedObjectModel对象。而使用模型文件的话通常都是经过NSManagedObjectModel对象来读取文件。

若是是纯代码的话,苹果更推荐使用KVC的方式存取值,而后全部托管对象都用NSManagedObject建立。可是这样存在的问题不少,开发成本比较大、使用不方便等等。最大的问题就是写属性名的key字符串,很容易出错,并且这样失去了CoreData原有的优势。因此仍是推荐使用.xcdatamodeld模型文件的开发方式。

建立托管对象类文件

建立文件

建立实体后,就能够根据对应的实体,生成开发中使用的基于NSManagedObject类的托管对象类文件。

仍是按照上面DepartmentEmployee的例子,先建立一个Department实体。由于Department实体有对多关系,生成托管对象类文件的关联属性不同,能够体现出和对一关系的区别,因此使用Department实体生成文件。

点击后缀名为.xcdatamodeld的模型文件,选择XcodeEditor -> Create NSManagedObject Subclass -> 选择模型文件 -> 选择实体,生成Department实体对应的托管对象类文件。

生成的托管对象类文件

能够看到上面生成了四个文件,以实体名开头的.h.m文件,另外两个是这个实体的Category文件。为何生成Category文件?一会再说,先打开类文件进去看看。

Category

实体Category

能够看到类文件中有两个Category,分别是CoreDataPropertiesCoreDataGeneratedAccessors。其中若是没有设置对多关系的实体,只会有CoreDataProperties,而设置了对多关系的实体系统会为其生成CoreDataGeneratedAccessors

CoreDataProperties中会生成实体中声明的AttributesRelationships中的属性,其中对多关系是用NSSet存储的属性,若是是对一的关系则是非集合的对象类型属性。再看.m文件中,全部属性都用@dynamic修饰,CoreData会在运行时动态为全部Category中的属性生成实现代码,因此这里用@dynamic修饰。

对多属性生成的CoreDataGeneratedAccessors,是系统自动生成管理对多属性集合的方法,通常都是一个属性对应四个方法,方法的实现也是在运行时动态实现的,方法都是用来操做集合对象的。

托管对象类文件

点击系统生成的托管对象类文件,此类是继承自NSManagedObject类的。能够看到里面很是干净,没有其余逻辑代码。

根据苹果的注释代码:Insert code here to declare functionality of your managed object subclass,提示应该在这个文件中编写此类相关的逻辑代码。这里就是编写此类逻辑代码的地方,固然也能够什么都不写,看需求啦。

任意类型属性

实体支持建立任意继承自NSObject类的属性,例如项目中手动建立的类。项目中建立的类在下拉列表中并不会体现,能够在属性类型选择transformable类型,而后生成托管对象类文件的时候,系统会将这个属性声明为id类型,在建立类文件后,能够直接手动更改这个属性的类型为咱们想要的类型。

对于手动设置的属性有一个要求,属性所属的类必须是遵照NSCoding协议,由于这个属性要被归档到本地。

标量类型

建立托管对象类文件时,实体属性的类型不管是选择的integer32仍是float,只要是基础数据类型,最后建立出来的默认都是NSNumber类型的,这是Xcode默认的。

若是须要生成的属性类型是基础数据类型,能够在建立文件时勾选Use scalar properties for primitive data types选项,这样就告诉系统须要生成标量类型属性,建立出来的属性就是int64_tfloat这样的基础数据类型。

标量类型

更新文件

当前模型对应的实体发生改变后,须要从新生成模型Category文件。生成步骤和上面同样,主要是替换Category文件,托管对象文件不会被替换。生成文件时不须要删除,直接替换文件


CoreData增删改查

下面关于CoreData的相关操做,仍是基于上面DepartmentEmployee的例子。而且引入了Company当作.xcdatamodeld模型文件,前面两个实体被包含在Company中。

先讲讲NSManagedObjectContext

iOS5以前建立NSManagedObjectContext对象时,都是直接经过init方法来建立。iOS5以后苹果更加推荐使用initWithConcurrencyType:方法来建立,在建立的时候指定当前是什么类型的并发队列,初始化方法参数是一个枚举值。这里简单说说MOC,后面多线程部分还会涉及MOC多线程相关的东西。

NSManagedObjectContext初始化方法的枚举值参数主要有三个类型:

  • NSConfinementConcurrencyType 若是使用init方法初始化上下文,默认就是这个并发类型。在iOS9以后已经被苹果废弃,不建议用这个API,调用某些比较新的CoreDataAPI可能会致使崩溃。

  • NSPrivateQueueConcurrencyType 私有并发队列类型,操做都是在子线程中完成的。

  • NSMainQueueConcurrencyType 主并发队列类型,若是涉及到UI相关的操做,应该考虑使用这个参数初始化上下文。

若是还使用init方法,可能会对后面推出的一些API不兼容,致使多线程相关的错误。例以下面的错误,由于若是没有显式的设置并发类型,默认是一个已经弃用的NSConfinementConcurrencyType类型,就会致使新推出的API发生不兼容的崩溃错误。

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSConfinementConcurrencyType context

建立MOC

下面是根据Company模型文件,建立了一个主队列并发类型的MOC

// 建立上下文对象,并发队列设置为主队列
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

// 建立托管对象模型,并使用Company.momd路径当作初始化参数
NSURL *modelPath = [[NSBundle mainBundle] URLForResource:@"Company" withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelPath];

// 建立持久化存储调度器
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];

// 建立并关联SQLite数据库文件,若是已经存在则不会重复建立
NSString *dataPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
dataPath = [dataPath stringByAppendingFormat:@"/%@.sqlite", @"Company"];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:nil error:nil];

// 上下文对象设置属性为持久化存储器
context.persistentStoreCoordinator = coordinator;

这段代码建立了一个MOC,咱们从上往下看这段代码。

momd文件

关于MOC的并发队列类型上面已经简单说了,MOC下面出现了momd的字样,这是什么东西?

momd文件

在建立后缀为.xcdatamodeld的模型文件后,模型文件在编译期将会被编译为后缀为.momd的文件,存放在.app中,也就是Main Bundle中。在存在多个模型文件时,咱们须要经过加载不一样的.momd文件,来建立不一样的NSManagedObjectModel对象,每一个NSManagedObjectModel对应着不一样的模型文件。

NSManagedObjectModel类中包含了模型文件中的全部entitiesconfigurationsfetchRequests的描述。虽然.momd文件是支持存放在.app中的,其余人能够经过打开.app包看到这个文件。可是这个文件是通过编码的,并不会知道这个.momd文件中的内容,因此这个文件是很是安全的。经过NSManagedObjectModel获取模型文件描述后,来建立和关联数据库,并交给PSC管理。

若是不指定NSManagedObjectModel对应哪一个模型文件,直接使用init方法初始化NSManagedObjectModel类,系统会默认将全部模型文件的表都放在一个SQLite数据库中。因此须要使用mainBundle中的不一样.momd文件,对不一样的NSManagedObjectModel进行初始化,这样在建立数据库时就会建立不一样的数据库文件。

持久化存储调度器(PSC)

NSManagedObjectModel下面就是NSPersistentStoreCoordinator,这个类在CoreData框架体系中起到了**“中枢”的做用。对上层起到了提供简单的调用接口,并向上层隐藏持久化实现逻辑。对下层起到了协调多个持久化存储对象**(NSPersistentStore),使下层只须要专一持久化相关逻辑。

持久化存储调度器

addPersistentStoreWithType: configuration: URL: options: error:方法是PSC建立并关联数据库的部分,关联本地数据库后会返回一个NSPersistentStore类型对象,这个对象负责具体持久化存储的实现。能够看到这个方法是一个实例方法,也就是能够添加多个持久化存储对象,而且多个持久化存储对象都关联一个PSC,这是容许的,在上面的图中也看到了这样的结构。可是这样的需求并很少,并且管理起来比较麻烦,通常都不会这样作。

PSC有四种可选的持久化存储方案,用得最多的是SQLite的方式。其中BinaryXML这两种方式,在进行数据操做时,须要将整个文件加载到内存中,这样对内存的消耗是很大的。

  • NSSQLiteStoreType : SQLite数据库
  • NSXMLStoreType : XML文件
  • NSBinaryStoreType : 二进制文件
  • NSInMemoryStoreType : 直接存储在内存中

插入操做

// 建立托管对象,并指明建立的托管对象所属实体名
Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:context];
emp.name = @"lxz";
emp.height = @1.7;
emp.brithday = [NSDate date];

// 经过上下文保存对象,并在保存前判断是否有更改
NSError *error = nil;
if (context.hasChanges) {
    [context save:&error];
}

// 错误处理
if (error) {
    NSLog(@"CoreData Insert Data Error : %@", error);
}

经过NSEntityDescriptioninsert类方法,生成并返回一个Employee托管对象,并将这个对象插入到指定的上下文中。

MOC将操做的数据存放在缓存层,只有调用MOCsave方法后,才会真正对数据库进行操做,不然这个对象只是存在内存中,这样作避免了频繁的数据库访问。

删除操做

// 创建获取数据的请求对象,指明对Employee实体进行删除操做
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 建立谓词对象,过滤出符合要求的对象,也就是要删除的对象
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];
request.predicate = predicate;

// 执行获取操做,找到要删除的对象
NSError *error = nil;
NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];

// 遍历符合删除要求的对象数组,执行删除操做
[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    [context deleteObject:obj];
}];

// 保存上下文
if (context.hasChanges) {
    [context save:nil];
}

// 错误处理
if (error) {
    NSLog(@"CoreData Delete Data Error : %@", error);
}

首先获取须要删除的托管对象,遍历获取的对象数组,逐个删除后调用MOCsave方法保存。

修改操做

// 创建获取数据的请求对象,并指明操做的实体为Employee
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 建立谓词对象,设置过滤条件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@", @"lxz"];
request.predicate = predicate;

// 执行获取请求,获取到符合要求的托管对象
NSError *error = nil;
NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    obj.height = @3.f;
}];

// 将上面的修改进行存储
if (context.hasChanges) {
    [context save:nil];
}

// 错误处理
if (error) {
    NSLog(@"CoreData Update Data Error : %@", error);
}

和上面同样,首先获取到须要更改的托管对象,更改完成后调用MOCsave方法持久化到本地。

查找操做

// 创建获取数据的请求对象,指明操做的实体为Employee
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

// 执行获取操做,获取全部Employee托管对象
NSError *error = nil;
NSArray<Employee *> *employees = [context executeFetchRequest:request error:&error];
[employees enumerateObjectsUsingBlock:^(Employee * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    NSLog(@"Employee Name : %@, Height : %@, Brithday : %@", obj.name, obj.height, obj.brithday);
}];

// 错误处理
if (error) {
    NSLog(@"CoreData Ergodic Data Error : %@", error);
}

查找操做最简单粗暴,由于是演示代码,因此直接将全部Employee表中的托管对象加载出来。在实际开发中确定不会这样作,只须要加载须要的数据。后面还会讲到一些更高级的操做,会涉及到获取方面的东西。

总结

CoreData中全部的托管对象被建立出来后,都是关联着MOC对象的。因此在对象进行任何操做后,都会被记录在MOC中。在最后调用MOCsave方法后,MOC会将操做交给PSC去处理,PSC将会将这个存储任务指派给NSPersistentStore对象。

上面的增删改查操做,看上去大致流程都差很少,都是一些最基础的简单操做,在下一篇文章中将会将一些比较复杂的操做。


好多同窗都问我有Demo没有,其实文章中贴出的代码组合起来就是个Demo。后来想了想,仍是给本系列文章配了一个简单的Demo,方便你们运行调试,后续会给全部博客的文章都加上Demo

Demo只是来辅助读者更好的理解文章中的内容,应该博客结合Demo一块儿学习,只看Demo仍是不能理解更深层的原理Demo中几乎每一行代码都会有注释,各位能够打断点跟着Demo执行流程走一遍,看看各个阶段变量的值。

Demo地址刘小壮的Github


这两天更新了一下文章,将CoreData系列的六篇文章整合在一块儿,作了一个PDF版的《CoreData Book》,放在我Github上了。PDF上有文章目录,方便阅读。

若是你以为不错,请把PDF帮忙转到其余群里,或者你的朋友,让更多的人了解CoreData,衷心感谢!

相关文章
相关标签/搜索