<简书 — 刘小壮> http://www.jianshu.com/p/0ddfa35c7898git
第一篇文章中并无讲
CoreData
的具体用法,只是对CoreData
作了一个详细的介绍,算是一个开始和总结吧。github这篇文章中会主要讲
CoreData
的基础使用,以及在使用中须要注意的一些细节。由于文章中会插入代码和图片,内容可能会比较多,比较考验各位耐心。正则表达式文章中若有疏漏或错误,还请各位及时提出,谢谢!`sql
在新建一个项目时,能够勾选Use Core Data
选项,这样建立出来的工程系统会默认生成一些CoreData
的代码以及一个.xcdatamodeld
后缀的模型文件,模型文件默认以工程名开头。这些代码在AppDelegate
类中,也就是表明能够在全局使用AppDelegate.h
文件中声明的CoreData
方法和属性。数据库
系统默认生成的代码是很是简单的,只是生成了基础的托管对象模型、托管对象上下文、持久化存储调度器,以及MOC
的save
方法。可是这些代码已经能够完成基础的CoreData
操做了。数组
这部分代码不该该放在AppDelegate
中,尤为对于大型项目来讲,更应该把这部分代码单独抽离出去,放在专门的类或模块来管理CoreData
相关的逻辑。因此我通常不会经过这种方式建立CoreData
,我通常都是新建一个**“干净”**的项目,而后本身往里面添加,这样对于CoreData
的完整使用流程掌握的也比较牢固。缓存
使用CoreData
的第一步是建立后缀为.xcdatamodeld
的模型文件,使用快捷键Command + N,选择Core Data -> Data Model -> Next
,完成模型文件的建立。安全
建立完成后能够看到模型文件左侧列表,有三个选项Entities
、Fetch Requests
、Configurations
,分别对应着实体、请求模板、配置信息。多线程
如今能够经过长按左侧列表下方的Add Entity
按钮,会弹出Add Entity
、Add Fetch Request
、Add Configuration
选项,能够添加实体、请求模板、配置信息。这里先选择Add Entity
来添加一个实体,命名为Person
。并发
添加Person
实体后,会发现一个实体对应着三部份内容,Attributes
、Relationships
、Fetched Properties
,分别对应着属性、关联关系、获取操做。
如今对Person
实体添加两个属性,添加age
属性并设置type
为Integer 16
,添加name
属性并设置type
为String
。
在模型文件的实体中,参数类型和平时建立继承自NSObject
的模型类大致相似,可是仍是有一些关于类型的说明,下面简单的列举了一下。
-32768 ~ 32767
-2147483648 ~ 2147483647
–9223372036854775808 ~ 9223372036854775807
MAXFLOAT
宏定义来看,最大值用科学计数法表示是 0x1.fffffep+127f
Float
更精确,表示范围更大NSString
表示NSNumber
表示NSDate
表示NSData
表示OC
对象,用id
表示。能够在建立托管对象类文件后,手动改成对应的OC
类名。使用的前提是,这个OC
对象必须遵照并实现NSCoding
协议建立两个实体Department
和Employee
,而且在这两个实体中分别添加一些属性,下面将会根据这两个实体来添加关联关系。
给Employee
实体添加关系,在Relationships
的位置点击加号,添加一个关联关系。添加关系的名称设为department
,类型设置为Department
,Inverse
设置为employee
(后面会讲解这个inverse
的做用)。
选择Department
实体,点击Relationships
位置的加号,添加关联关系。(须要注意的是,inverse
须要设置好Relationships
以后才能设置)
Department
实体添加Relationships
的操做和Employee
都同样,区别在于用红圈标出的Type
,这里设置的To Many
一对多的关系。这里默认是To One
一对一,上面的Employee
就是一对一的关系。也就符合一个Department
能够有多个Employee
,而Employee
只能有一个Department
的状况,这也是符合常理的。
Relationships
相似于SQLite
的外键,定义了在同一个模型中,实体与实体之间的关系。能够定义为对一关系或对多关系,也能够定义单向或双向的关系,根据需求来肯定。若是是对多的关系,默认是使用NSSet
集合来存储模型。
Inverse
是两个实体在Relationships
中设置关联关系后,经过设置inverse
为对应的实体,这样能够从一个实体找到另外一个实体,使两个实体具备双向的关联关系。
在实体最下面,有一个Fetched Properties
选项,这个选项用的很少,这里就不细讲了。
Fetched Properties
用于定义查询操做,和NSFetchRequest
功能相同。定义fetchedProperty
对象后,能够经过NSManagedObjectModel
类的fetchRequestFromTemplateWithName:substitutionVariables:
方法或其余相关方法获取这个fetchedProperty
对象。
获取这个对象后,系统会默认将这个对象缓存到一个字典中,缓存以后也能够经过fetchedProperty
字典获取fetchedProperty
对象。
选中一个实体后,右侧的侧边栏(Data Model Inspector
)还有不少选项,这些选项能够对属性进行配置。根据不一样的属性类型,侧边栏的显示也不太同样,下面是一个String
类型的属性。
default Value: 设置默认值,除了二进制不能设置,其余类型几乎都能设置。
optional: 在使用时是否可选,也能够理解为若是设置为NO
,只要向MOC
进行save
操做,这个属性是否必须有值。不然MOC
进行操做时会失败并返回一个error
,该选项默认为YES
。
transient: 设置当前属性是否只存在于内存,不被持久化到本地,若是设置为YES
,这个属性就不参与持久化操做,属性的其余操做没有区别。transient
很是适合存储一些在内存中缓存的数据,例如存储临时数据,这些数据每次都是不一样的,并且不须要进行本地持久化,因此能够声明为transient
的属性。
indexed: 设置当前属性是不是索引。添加索引后能够有效的提高检索操做的速度。可是对于删除这样的操做,删除索引后其余地方还须要作出相应的变化,因此速度会比较慢。
Validation: 经过Validation
能够设置Max Value
和Min Value
,经过这两个条件来约定数据,对数据的存储进行一个验证。数值类型都有相同的约定方式,而字符串则是约定长度,date
是约定时间。
Reg. Ex.(Regular Expression
): 能够设置正则表达式,用来验证和控制数据,不对数据自身产生影响。(只能应用于String
类型)
Allows External Storage: 当存储二进制文件时,若是遇到比较大的文件,是否存储在存储区以外。若是选择YES
,存储文件大小超过1MB
的文件,都会存储在存储区以外。不然大型文件存储在存储区内,会形成SQLite
进行表操做时,效率受到影响。
NSNoActionDeleteRule 删除后没有任何操做,也不会将关联对象的关联属性指向nil
。删除后使用关联对象的关联属性,可能会致使其余问题。 NSNullifyDeleteRule 删除后会将关联对象的关联属性指向nil
,这是默认值。 NSCascadeDeleteRule 删除当前对象后,会将与之关联的对象也一并删除。 NSDenyDeleteRule 在删除当前对象时,若是当前对象还指向其余关联对象,则当前对象不能被删除。
To One
和To Many
,表示当前关系是一对多仍是一对一。Parent Entity
能够设置父实体,这样就存在了实体的继承关系,最后建立出来的托管模型类也是具备继承关系的。注意继承关系中属性名不要相同。 使用了这样的继承关系后,系统会将子类继承父类的数据,存在父类的表中,全部继承自同一父类的子类都会将父类部分存放在父类的表中。这样可能会致使父类的表中数据量过多,形成性能问题。在模型文件中Entities
下面有一个Fetch Requests
,这个也是配置请求对象的。可是这个使用起来更加直观,能够很容易的完成一些简单的请求配置。相对于上面讲到的Fetched Properties
,这个仍是更方便使用一些。
上面是对Employee
实体的height
属性配置的Fetch Request
,这里配置的height
要小于2米。配置以后能够经过NSManagedObjectModel
类的fetchRequestTemplateForName:
方法获取这个请求对象,参数是这个请求配置的名称,也就是EmployeeFR
。
这是我认为CoreData
最大的优点之一,可视化的模型文件结构。能够很清楚的看到实体和属性的关系,以及实体之间的对应关系。
一个.xcdatamodeld
模型文件的展现风格有两种,一种是列表的形式(Table
),另外一种是图表的形式展现(Graph
)。
图表看起来更加直观,而图表在操做上也有一些比Table
更方便的地方。例如在Table
的状态下添加两个实体的关联关系,若是只作一次关联操做,默认是单向的关系。而在Graph
的状态下,按住Control
对两个图表进行连线,两个实体的结果就是双向关联的关系。
假设不使用.xcdatamodeld
模型文件,全都是纯代码,怎么在项目里建立实体啊?这样的话就须要经过代码建立实体描述、关联描述等信息,而后设置给NSManagedObjectModel
对象。而使用模型文件的话通常都是经过NSManagedObjectModel
对象来读取文件。
若是是纯代码的话,苹果更推荐使用KVC
的方式存取值,而后全部托管对象都用NSManagedObject
建立。可是这样存在的问题不少,开发成本比较大、使用不方便等等。最大的问题就是写属性名的key
字符串,很容易出错,并且这样失去了CoreData
原有的优势。因此仍是推荐使用.xcdatamodeld
模型文件的开发方式。
建立实体后,就能够根据对应的实体,生成开发中使用的基于NSManagedObject
类的托管对象类文件。
仍是按照上面Department
和Employee
的例子,先建立一个Department
实体。由于Department
实体有对多关系,生成托管对象类文件的关联属性不同,能够体现出和对一关系的区别,因此使用Department
实体生成文件。
点击后缀名为.xcdatamodeld
的模型文件,选择Xcode
的Editor -> Create NSManagedObject Subclass -> 选择模型文件 -> 选择实体
,生成Department
实体对应的托管对象类文件。
能够看到上面生成了四个文件,以实体名开头的.h
和.m
文件,另外两个是这个实体的Category
文件。为何生成Category
文件?一会再说,先打开类文件进去看看。
能够看到类文件中有两个Category
,分别是CoreDataProperties
和CoreDataGeneratedAccessors
。其中若是没有设置对多关系的实体,只会有CoreDataProperties
,而设置了对多关系的实体系统会为其生成CoreDataGeneratedAccessors
。
CoreDataProperties
中会生成实体中声明的Attributes
和Relationships
中的属性,其中对多关系是用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_t
、float
这样的基础数据类型。
当前模型对应的实体发生改变后,须要从新生成模型Category
文件。生成步骤和上面同样,主要是替换Category
文件,托管对象文件不会被替换。生成文件时不须要删除,直接替换文件。
下面关于CoreData
的相关操做,仍是基于上面Department
和Employee
的例子。而且引入了Company
当作.xcdatamodeld
模型文件,前面两个实体被包含在Company
中。
在iOS5
以前建立NSManagedObjectContext
对象时,都是直接经过init
方法来建立。iOS5
以后苹果更加推荐使用initWithConcurrencyType:
方法来建立,在建立的时候指定当前是什么类型的并发队列,初始化方法参数是一个枚举值。这里简单说说MOC
,后面多线程部分还会涉及MOC
多线程相关的东西。
NSManagedObjectContext
初始化方法的枚举值参数主要有三个类型:
NSConfinementConcurrencyType 若是使用init
方法初始化上下文,默认就是这个并发类型。在iOS9
以后已经被苹果废弃,不建议用这个API
,调用某些比较新的CoreData
的API
可能会致使崩溃。
NSPrivateQueueConcurrencyType 私有并发队列类型,操做都是在子线程中完成的。
NSMainQueueConcurrencyType 主并发队列类型,若是涉及到UI
相关的操做,应该考虑使用这个参数初始化上下文。
若是还使用init
方法,可能会对后面推出的一些API
不兼容,致使多线程相关的错误。例以下面的错误,由于若是没有显式的设置并发类型,默认是一个已经弃用的NSConfinementConcurrencyType
类型,就会致使新推出的API
发生不兼容的崩溃错误。
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSConfinementConcurrencyType context
下面是根据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
,咱们从上往下看这段代码。
关于MOC
的并发队列类型上面已经简单说了,MOC
下面出现了momd
的字样,这是什么东西?
在建立后缀为.xcdatamodeld
的模型文件后,模型文件在编译期将会被编译为后缀为.momd
的文件,存放在.app
中,也就是Main Bundle
中。在存在多个模型文件时,咱们须要经过加载不一样的.momd
文件,来建立不一样的NSManagedObjectModel
对象,每一个NSManagedObjectModel
对应着不一样的模型文件。
NSManagedObjectModel
类中包含了模型文件中的全部entities
、configurations
、fetchRequests
的描述。虽然.momd
文件是支持存放在.app
中的,其余人能够经过打开.app
包看到这个文件。可是这个文件是通过编码的,并不会知道这个.momd
文件中的内容,因此这个文件是很是安全的。经过NSManagedObjectModel
获取模型文件描述后,来建立和关联数据库,并交给PSC
管理。
若是不指定NSManagedObjectModel
对应哪一个模型文件,直接使用init
方法初始化NSManagedObjectModel
类,系统会默认将全部模型文件的表都放在一个SQLite
数据库中。因此须要使用mainBundle
中的不一样.momd
文件,对不一样的NSManagedObjectModel
进行初始化,这样在建立数据库时就会建立不一样的数据库文件。
在NSManagedObjectModel
下面就是NSPersistentStoreCoordinator
,这个类在CoreData
框架体系中起到了**“中枢”的做用。对上层起到了提供简单的调用接口,并向上层隐藏持久化实现逻辑。对下层起到了协调多个持久化存储对象**(NSPersistentStore
),使下层只须要专一持久化相关逻辑。
addPersistentStoreWithType: configuration: URL: options: error:
方法是PSC
建立并关联数据库的部分,关联本地数据库后会返回一个NSPersistentStore
类型对象,这个对象负责具体持久化存储的实现。能够看到这个方法是一个实例方法,也就是能够添加多个持久化存储对象,而且多个持久化存储对象都关联一个PSC
,这是容许的,在上面的图中也看到了这样的结构。可是这样的需求并很少,并且管理起来比较麻烦,通常都不会这样作。
PSC
有四种可选的持久化存储方案,用得最多的是SQLite
的方式。其中Binary
和XML
这两种方式,在进行数据操做时,须要将整个文件加载到内存中,这样对内存的消耗是很大的。
// 建立托管对象,并指明建立的托管对象所属实体名 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); }
经过NSEntityDescription
的insert
类方法,生成并返回一个Employee
托管对象,并将这个对象插入到指定的上下文中。
MOC
将操做的数据存放在缓存层,只有调用MOC
的save
方法后,才会真正对数据库进行操做,不然这个对象只是存在内存中,这样作避免了频繁的数据库访问。
// 创建获取数据的请求对象,指明对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); }
首先获取须要删除的托管对象,遍历获取的对象数组,逐个删除后调用MOC
的save
方法保存。
// 创建获取数据的请求对象,并指明操做的实体为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); }
和上面同样,首先获取到须要更改的托管对象,更改完成后调用MOC
的save
方法持久化到本地。
// 创建获取数据的请求对象,指明操做的实体为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
中。在最后调用MOC
的save
方法后,MOC
会将操做交给PSC
去处理,PSC
将会将这个存储任务指派给NSPersistentStore
对象。
上面的增删改查操做,看上去大致流程都差很少,都是一些最基础的简单操做,在下一篇文章中将会将一些比较复杂的操做。
好多同窗都问我有Demo
没有,其实文章中贴出的代码组合起来就是个Demo
。后来想了想,仍是给本系列文章配了一个简单的Demo
,方便你们运行调试,后续会给全部博客的文章都加上Demo
。
Demo
只是来辅助读者更好的理解文章中的内容,应该博客结合Demo
一块儿学习,只看Demo
仍是不能理解更深层的原理。Demo
中几乎每一行代码都会有注释,各位能够打断点跟着Demo
执行流程走一遍,看看各个阶段变量的值。
Demo地址:刘小壮的Github
这两天更新了一下文章,将CoreData
系列的六篇文章整合在一块儿,作了一个PDF
版的《CoreData Book》,放在我Github上了。PDF
上有文章目录,方便阅读。
若是你以为不错,请把PDF帮忙转到其余群里,或者你的朋友,让更多的人了解CoreData,衷心感谢!