CoreData数据迁移
轻数据迁移:https://developer.apple.com/documentation/coredata/using_lightweight_migration
重数据迁移:https://developer.apple.com/documentation/coredata/heavyweight_migration
核心数据模型版本控制和数据迁移编程指南:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmMappingOverview.html#//apple_ref/doc/uid/TP40004399-CH5-SW2
渐进式迁移:https://github.com/objcio/issue-4-core-data-migration
如果想要看一下文件存储的数据,可以在线sqlite文件查看
对于coredata的使用,苹果也是尽量的把coredata操作简化,所以现在在正常的开发中,coredata已经是非常易用的了。但是网上的相关文章还是少了,懂得不难,难的不懂,所以遇到了问题的话要解决起来还是很麻烦的。
这里做的版本迁移,其实懂的话也是非常简单的,苹果那边都已经说明了,但是没有示例代码,还是很难实现出来的,而且坑爹的是苹果的文档是好多年前的,现在已经改了一些api。
版本迁移的选择
如果我们改动了数据库,那么数据的结构就会和之前存储在本地的不同,这个时候会因为结构的不同而导致崩溃。但是如果卸载app重新安装就不会,因为没有之前存储在本地的数据,所以不会有数据结构的不同。为解决数据结构不同的问题,我们需要做数据库的版本迁移。
轻数据迁移
此方案满足的条件是大致有几个:满足条件的官方文档
- 添加、删除一个表或者属性
- 属性值可空与不可空的改变
- 重命名表或者属性
- 单一的一次迁移(如果出现多次版本迁移要一起做,就要进行逐步迁移,不能用轻数据迁移)
1、创建迁移版本
通过一个参照版本,创建一个迁移版本,之后我们的表改动都在新的迁移版本中操作,参照版本不要改动
2、更改使用的数据库版本
新创建的版本还需要应用才可以。统一通过设置当前版本来表示最终这个数据库的结构到底是怎么样的
在数据库文件中,右键查看包内容,可以看到所有的数据库版本文件
3、创建映射文件
创建mapping model文件,选择源数据库和目标数据库
4、完成迁移
自此,我们就可以完成全自动的轻数据迁移了,甚至连一行代码都不需要写。因为苹果做了一些优化,iOS10后出现了NSPersistentContainer这个类,对各个能力的类都进行了默认管理,所以只需要有mapping model映射文件,就可以启动迁移数据。以前的话,好惨的,要代码写各种迁移对象。
重数据迁移(自定义迁移)
此方案满足的条件是大致有几个:
- 属性的数据类型改变了
- 考虑多版本迁移
- 想要灵活处理数据,Mapping Model的映射关系不能解决
1、创建自定义迁移策略类
继承于“NSEntityMigrationPolicy”,这里有几个方法主要是在迁移的各个阶段的时候,对一些对应关系进行设置确认,这样就可以通过代码灵活处理各种情况。我这边只使用了一个“createDestinationInstances”方法,这是创建目标实例的方法,作用就是设置目标实例的值,那么就会迁移的时候目标数据库的值就是确定的了。
下面是自定义迁移策略代码
也可以直接下载文件自定义迁移策略类
///创建迁移数据
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
//不用走super.createDestinationInstances.如果走的话就不需要走下面的自定义逻辑
//super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
//获取所有属性
let sourceKeys = sInstance.entity.attributesByName.keys
//生成对应的属性和值
let sourceValues = sInstance.dictionaryWithValues(forKeys: sourceKeys.sorted())
//插入一个新的目标实例(相当于插入一行数据)
let destinationInstance = NSEntityDescription.insertNewObject(forEntityName: mapping.destinationEntityName!, into: manager.destinationContext)
//目标实例的所有属性
let destinationKeys = destinationInstance.entity.attributesByName.keys;
for key in destinationKeys {
//这里可以处理数据了,赋值目标数据
if key == "times" {
guard let minute = sourceValues["minute"] as? String, let minuteInt16 = Int16(minute),
let hour = sourceValues["hour"] as? String, let hourInt16 = Int16(hour)
else { return }
destinationInstance.setValue(hourInt16 + minuteInt16, forKey: key)
} else {
if let value = sourceValues[key] {
destinationInstance.setValue(value, forKey: key)
}
}
}
print(destinationInstance)
}
2、指定映射文件的迁移策略
在mapping model文件中,输入我们的自定义迁移策略类,那么在迁移的时候,就不是走系统默认的copy映射,而是会走到我们的自定义类中,在里面我们可以是指每个字段的所有数据。
这里有坑
这里有一个巨坑的问题,不知道是不是因为项目带了特殊字符,所以需要写上项目名称,而且在设置mapping的策略类的项目目录命名有要求(如果项目中有这些特殊字符,不自己写转换后的“_”,必定报错说找不到这个策略,无法初始化)
3、验证迁移
到了这里,我们就可以实现自定义迁移了,自定义迁移能做的事情主要是让我们可以自由的设置每个字段的值。我们在使用coredata的时候,系统会自动监测是否需要迁移,如果需要就会自动迁移。(但是这仅限于有指定映射文件的迁移,如果是差了好几个版本的数据库迁移,找不到对应的映射文件,还是会报错)
渐进式迁移
我们开发的时候都是一个版本一个版本开发,所以苹果这边提供的版本迁移逻辑是没有问题的。但是实际的情况是,我们的用户可能不经常更新app,所以有可能他再次更新app的时候,我们的coredata数据库已经改了几个版本,做了多次的数据迁移。这时候用户更新app,本地数据是v1版本,直接数据迁移我们的v4版本,这时就没有对应的v1->v4的映射文件,就不知道怎么做数据迁移,所以这种跨越版本的数据迁移我们无法确认映射文件。可行的方案是逐步迁移,v1->v2->v3->v4我们每次只迁移一个版本。
递归迁移
这里我们最中心的思想就是递归,根据原数据推断对应的mapping model文件,然后一次次的迁移到目标模型,然后再把目标模型当做源数据推断下一个mapping model迁移,直到没有数据结构差异或者没有mapping model才停止迁移。
渐进式迁移逻辑代码
代码比较多,直接下载就可以了,把文件放到项目中,在启动项目适当的地方调用即可