CoreData¶
对象关系映射(英语称object Relational Mapping,简称ORM)。
ORM是通过使用描述对象和数据库之间映射的元数据,可以实现将对象自动持久化到关系数据库当中。
使用CoreData过程中不需要我们编写SQL语句,也就是将OC对象存储于数据库,也可以将数据库数据转为OC对象(数据库数据与OC对象相互转换)。
CoreData几个类¶
数据模型文件 - Data Model¶
当我们用Core Data
时,我们需要一个用来存放数据模型的地方,数据模型文件就是我们要创建的文件类型。它的后缀是.xcdatamodeld
。只要在项目中选 新建文件→Data Model 即可创建。
默认系统提供的命名为 Model.xcdatamodeld
。
这个文件就相当于数据库中的“库”。通过编辑这个文件,就可以去添加定义自己想要处理的数据类型。
数据模型中的“表格” - Entity实体¶
Entity
中文翻译叫“实体”。如果把数据模型文件比作数据库中的“库”,那么Entity
就相当于库里的“表格”。Entity
就是让你定义数据表格类型的名词。
假设我这个数据模型是用来存放图书馆信息的,那么就建立一个叫Book
的Entity
。
生成对应实体的实体类¶
- 先选择Code Generation,Language选择OC还是Swift
- Xcode -- Editor -- Create NSManagedObject Subclass
“属性” - Attributes¶
当建立一个名为Book
的Entity
时,会看到视图中有栏写着Attributes
,我们知道,当我们定义一本书时,自然要定义书名,书的编码等信息。这部分信息叫Attributes
,即书的属性。
Book的Entity
:
属性名 | 类型 |
---|---|
name | String |
isbm | String |
page | Integer32 |
同理,也可以再添加一个读者:Reader的Entity
描述。
Reader的Entity
:
属性名 | 类型 |
---|---|
name | String |
idCard | String |
“关系” - Relationship¶
在我们使用Entity
编辑时,除了看到了Attributes
一栏,还看到下面有Relationships
一栏。
当定义图书馆信息时,书籍和读者的信息,这两个信息彼此是孤立的,而事实上他们存在着联系。
比如一本书,它被某个读者借走了,这样的数据该怎么存储?
直观的做法是再定义一张表格来处理这类关系。但是Core Data
提供了更有效的办法 - Relationship
。
从Relationship
的思路来思考,当一本书A被某个读者B借走,我们可以理解为这本书A当前的“借阅者”是该读者B,而读者B的“持有书”是A。
从以上描述可以看出,Relationship
所描述的关系是双向的,即A和B互相以某种方式形成了联系,而这个方式是我们来定义的。
在Reader
的Relationship
下点击+
号键。然后在Relationship
栏的名字上填borrow
,表示读者和书的关系是“借阅”,在Destination
栏选择Book
,这样,读者和书籍的关系就确立了。
对于第三栏,Inverse
,却没有东西可以填,这是为什么?
因为我们现在定义了读者和书的关系,却没有定义书和读者的关系。记住,关系是双向的。
就好比你定义了A是B的父亲,那也要同时去定义B是A的儿子一个道理。计算机不会帮我们打理另一边的联系。
理解了这点,我们开始选择Book
的一栏,在Relationship
下添加新的borrowBy
,Destination
是Reader
,这时候点击Inverse
一栏,会发现弹出了borrow
,直接点上。
这是因为我们在定义Book
的Relationship
之前,我们已经定义了Reader
的Relationship
了,所以电脑已经知道了读者和书籍的关系,可以直接选上。而一旦选好了,那么在Reader
的Relationship
中,我们会发现Inverse
一栏会自动补齐为borrowBy
。因为电脑这时候已经完全理解了双方的关系,自动做了补齐。
“一对一”和“一对多” - to one和to many¶
我们建立Reader
和Book
之间的联系的时候,发现他们的联系逻辑之间还漏了一个环节。
假设一本书被一个读者借走了,它就不能被另一个读者借走,而当一个读者借书时,却可以借很多本书。
也就是说,一本书只能对应一个读者,而一个读者却可以对应多本书。
这就是 一对一→to one
和 一对多→to many
。
Core Data
允许我们配置这种联系,具体做法就是在RelationShip
栏点击对应的关系栏,它将会出现在右侧的栏目中。
在Relationship
的配置项里,有一项项名为Type
,点击后有两个选项,一个是To One
(默认值),另一个就是To Many
了。
Entity的Attributes中Type是字典、数组或者其他类型的时候¶
选择Transformable类型,这个类型从字面意思来理解为可转换类型。选中这条数据,如下图
可以看到在Trasnformer这栏填上自己定义的类,这个类需要继承于ValueTransformer
import Foundation
@objc(ArrayMoedl)
final class ArrayMoedl: ValueTransformer {
override func transformedValue(_ value: Any?) -> Any? {
guard let value = value as? Array<Any> else{
return nil
}
do {
if #available(iOS 11.0, *) {
let data = try NSKeyedArchiver.archivedData(withRootObject:value, requiringSecureCoding: true)
return data
} else {
// Fallback on earlier versions
return NSKeyedArchiver.archivedData(withRootObject:value)
}
}catch{
assertionFailure("Failed to transform 'Array' to 'Data'")
return nil
}
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? NSData else{return nil}
do {
if #available(iOS 11.0, *) {
let result = try NSKeyedUnarchiver.unarchivedObject(ofClass:NSArray.self, from:data as Data)
return result
}else {
guard let result = NSKeyedUnarchiver.unarchiveObject(with: data as Data)else{
return nil
}
return result
}
}catch{
assertionFailure("Failed to transform 'Data' to 'Array'")
return nil
}
}
override class func allowsReverseTransformation() -> Bool {
return true
}
}
上面的Array也可以改为字典或者其他自定义类型。
NSPersistentContainer¶
持久化容器
NSManagedObjectContext¶
NSManagedObjectContext意思是托管对象上下文,数据库的大多数操作是在这个类操作。
NSManagedObject¶
托管对象类,CoreData里面的托管对象(实体模型对象)都会继承此类。
- 定义一个数据模型类Person,继承自NSManagedObject。
- 在Person类中定义实体的属性,并使用@NSManaged修饰符告诉编译器这些属性将由CoreData管理。
编译报错¶
Multiple commands produce '路径/Student+CoreDataClass.o':
1) Target 'HHCoreDataDemo' (project 'HHCoreDataDemo') has compile command with input '路径/Student+CoreDataClass.m'
2) Target 'HHCoreDataDemo' (project 'HHCoreDataDemo') has compile command with input '路径/Student+CoreDataClass.m'
Multiple commands produce '路径/Student+CoreDataProperties.o':
1) Target 'HHCoreDataDemo' (project 'HHCoreDataDemo') has compile command with input '路径/Student+CoreDataProperties.m'
2) Target 'HHCoreDataDemo' (project 'HHCoreDataDemo') has compile command with input '路径/Student+CoreDataProperties.m'
解决方法:
需要在Build Phases
中Compile Sources
删除+CoreDataClass.m
和+CoreDataProperties.m
,不需要编译那两个文件。
版本迁移¶
CoreData
版本迁移的方式有很多,一般都是先在Xcode
中,原有模型文件的基础上,创建一个新版本的模型文件,然后在此基础上做不同方式的版本迁移。
为什么要版本迁移¶
在已经运行程序并通过模型文件生成数据库后,再对模型文件进行的修改,如果只是修改已有实体属性的默认值、最大最小值、Fetch Request
等属性自身包含的参数时,并不会发生错误。如果修改模型文件的结构,或修改属性名、实体名等,造成模型文件的结构发生改变,这样再次运行程序就会导致崩溃。
在开发测试过程中,可以直接将原有程序卸载就可以解决这个问题,但是本地之前存储的数据也会消失。如果是线上程序,就涉及到版本迁移的问题,否则会导致崩溃,并提示错误。
然而在需求不断变化的过程中,后续版本肯定会对原有的模型文件进行修改,这时就需要用到版本迁移的技术,下面开始讲版本迁移的方案。
创建新版本模型文件¶
本文中讲的几种版本迁移方案,在迁移之前都需要对原有的模型文件创建新版本。
选中需要做迁移的模型文件 -> 点击菜单栏Editor -> Add Model Version -> 选择基于哪个版本的模型文件(一般都是选择目前最新的版本),新建模型文件完成。
对于新版本模型文件的命名,我在创建新版本模型文件时,一般会拿当前工程版本号当做后缀,这样在模型文件版本比较多的时候,就可以很容易将模型文件版本和工程版本对应起来。
添加完成后,会发现之前的模型文件会变成一个文件夹,里面包含着多个模型文件。