Core Data 版本迁移经验总结

大家在学习和使用Core Data过程中,第一次进行版本迁移的经历一定是记忆犹新,至少我是这样的,XD。弄的不好,就会搞出一些由于迁移过程中数据模型出错导致的Crash。这里总结了一下Core Data版本迁移过程中的经验,希望对大家有用。

写在前面

关于Core Data版本迁移,这两篇文章都进行了分析,大家可以参考。

Core Data Model Versioning and Data Migration Programming Guide

自定义 Core Data 迁移

迁移准备

1) 选中工程中的xcdaramodeId文件,Menu->Editor->Add Model Version

这一步添加完成之后,工程中的*xcdaramodeId* 文件将会被展开,并且出现了新增加的Model文件

2) 在Xcode右侧的辅助工具栏中找到 Model Version, 选择刚刚添加的Model文件,这个时候你会发现Xcode目录中,Model文件上的绿色的勾选中了当前选择的Model文件

3) 在新的Model文件中修改最新的Entities等信息,记得也同时修改NSManagedObject Subclass对应的实现

4) 修改NSPersistentStoreCoordinator部分实现:

let modelFilename = "the model file name in your project"

let modelPath = NSBundle.mainBundle().pathForResource(modelFIlename, ofType: "momd")

let managedObjectModel = NSManagedObjectModel(contentsOfURL: NSURL.fileURLWithPath(modelPath)

let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

// 这里是添加的部分,名如其意,当我们需要自动版本迁移时,我们需要在addPersistentStoreWithType方法中设置如下options

let options = [NSInferMappingModelAutomaticallyOption: true, NSMigratePersistentStoresAutomaticallyOption: true]

var error: NSError? = nil

persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error: &error)

轻量级迁移

当我们仅仅是对数据模型增加实体或者可选属性时,上述步骤完成后运行代码进行迁移是奏效的。这个过程文档中叫做Lightweight Migration,当我们进行轻量级迁移时,NSPersistentStoreCoordinator会为我们自动推断出一个Mapping Model。如果有更加复杂的改变,我们就需要自己去实现Mapping Mode。

添加Mapping Model过程: New File->CoreData->Mapping Model, 选择我们需要进行Mapping的两个Model,最终会生成一个 *xcmappingmodel* 文件,大家可以打开文件,看到里面生成了Model之间的映射。

官方文档中介绍如下的改变支持轻量级迁移:

为Entity简单的添加一个属性

为Entity移除一个属性

属性值由 Optional<-> Non-optional 之间转换

为属性设置 Default Value

重命名Entity或者Attribute

增加一个新的relationship 或者删除一个已经存在的 relationship

重命名relationship

改变relationship to-one<-> to-many 等

增加,删除Entities

增加新的 Parent 或者 Child Entity

从Hierarchy中移除Entities

轻量级迁移不支持合并Entity的层级:比如在旧的Model中两个已知的Entities没有共享一个共同的Parent Entity,那么在新的Model中它们也不能够共享一个共同的Parent Entity。

在为属性或者Entity等重命名时,我们需要在Xcode右侧辅助工具栏中找到 Versioning -> RenamingID,设置Reanaming Identifier为之前对应的名称。

Mapping Models

如果我们对数据模型的修改不支持轻量级迁移,我们就需要像上文中所说的那样,自己创建Mapping Model。

打开创建好的xcmappingmodel文件,我们发现可以增加或者修改对应的 Entity Mappings, Attibute Mappings 和 Relationship Mappings。

Core Data提供了如下一组变量允许我们进行配置:

NSMigrationManagerKey: $manager

NSMigrationSourceObjectKey: $source

NSMigrationDestinationObjectKey: $destination

NSMigrationEntityMappingKey: $entityMapping

NSMigrationPropertyMappingKey: $propertyMapping

NSMigrationEntityPolicyKey: $entityPolicy

有时候,我们不仅仅需要修改Entity的属性或者关系,可以使用NSEntityMigrationPolicy自定义整个迁移的过程。继承NSEntityMigrationPolicy实现迁移过程,然后选中对应的Entity Mapping,在Xcode右侧辅助工具栏中找到Custom Policy,并设置为实现迁移对应的类名。

NSEntityMigrationPolicy

NSEntityMigrationPolicy目前提供了7个方法可供实现,它们的调用顺序如下:

1) 当迁移将要开始时,会调用

func beginEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool

2) 在旧数据上构建新的实例时调用

func createDestinationInstancesForSourceInstance(sInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool

结束时调用

func endInstanceCreationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool

3) 构建新的RelationShips调用

func createRelationshipsForDestinationInstance(dInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool`

结束时调用

func endRelationshipCreationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool

4) 验证,保存数据调用

func performCustomValidationForEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool

5) 迁移结束时调用

func endEntityMapping(mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool

迁移过程

这里分享的是自己项目中数据自定义迁移的整个过程,并附上部分代码实现逻辑。项目中采用的是渐进式迁移,渐进式迁移的概念在自定义 Core Data 迁移一文中有介绍:

// 这段文字取自 <<自定义 Core Data 迁移>> 一文

想像一下你刚刚部署一个包含版本 3 的数据模型的更新。你的某个用户已经有一段时间没有更新你的应用了,这个用户还在版本 1 的数据模型上。那么现在你就需要一个从版本 1 到版本 3 的映射模型。同时你也需要版本 2 到版本 3 的映射模型。当你添加了版本 4 的数据模型后,那你就需要创建三个新的映射模型。显然这样做的扩展性很差,那就来试试渐进式迁移吧。

与其为每个之前的数据模型到最新的模型间都建立映射模型,还不如在每两个连续的数据模型之间创建映射模型。以前面的例子来说,版本 1 和版本 2 之间需要一个映射模型,版本 2 和版本 3 之间需要一个映射模型。这样就可以从版本 1 迁移到版本 2 再迁移到版本 3。显然,使用这种迁移的方式时,若用户在较老的版本上迁移过程就会比较慢,但它能节省开发时间并保证健壮性,因为你只需要确保从之前一个模型到新模型的迁移工作正常即可,而更前面的映射模型都已经经过了测试。

1) 判断本地SQLite数据库文件是否存在,不存在直接退出整个迁移。

2) 检测当前本地数据库和数据模型是否一致,如果一致就退出迁移。

let storeURL = NSURL(fileURLWithPath: "SQLite file path")

let managedObjectModel: NSManagedObjectModel  =  Your current managed object model

let sourceMetadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(NSSQLiteStoreType, URL:storeURL!, error: nil)

Bool needMigration = managedObjectModel.isConfiguration(nil, compatibleWithStoreMetadata: sourceMetadata)

3) 取得当前数据库存储时用的数据模型,如果获取不到或者获取失败,退出迁移。

if let sourceModel = NSManagedObjectModel.mergedModelFromBundles(nil, forStoreMetadata: sourceMetadata!) {

println("\(sourceModel)")

} else {

return

}

4) 取得当前工程中所有数据模型对应的managedObjectModel用于迁移,如果获取的结果少于两个就退出迁移。

5) 从所有的managedObjectModel中遍历出最终使用的Model和当前数据库采用的Model之间的所有Model,按照version顺序 构建一个新的 Model list,确保第一个是sourceModel,最后一个是当前需要使用的managedObjectModel。

6) 对生成的这个Model list进行循环,开始渐进式迁移。

7) 构建Model list中相邻两个Model之间的NSMappingModel实例,用做迁移。

for var index = 0; index < modelList.count - 1; index++ {

let modelA = modelList[index]

let modelB = modelList[index + 1]

//检查是否有自定义的Mapping model存在

var mappingModel : NSMappingModel? = NSMappingModel(fromBundles: nil, forSourceModel: modelA, destinationModel: modelB)

//如果不存在,尝试infer一个

mappingModel = NSMappingModel.inferredMappingModelForSourceModel(modelA, destinationModel: modelB, error: nil)

//如果最终取不到Mapping Model 就退出迁移

//如果得到了Mapping Model,就可以开始进行迁移

}

8) 终于可以开始进行迁移了,XD

9) 创建一个新的文件路径用来存储迁移过程中的数据文件

10) 使用上文中的 modelA 和 modelB 构建一个NSMigrationManager实例,使用

func migrateStoreFromURL(sourceURL: NSURL, type sStoreType: String, options sOptions: [NSObject : AnyObject]?, withMappingModel mappings: NSMappingModel?, toDestinationURL dURL: NSURL, destinationType dStoreType: String, destinationOptions dOptions: [NSObject : AnyObject]?, error: NSErrorPointer) -> Bool

方法进行迁移,其中toDestinationURL参数是我们在步骤9中创建的路径。如果migrate失败,就退出整个迁移过程。

11) 数据替换 1.把原始的source文件移动到新的backup文件夹中 2.使用步骤9中文件下生成的迁移之后的数据文件移动到原始的数据的路径下 3.删除backup

12) 回到步骤7,进行下一次迭代迁移,直到结束

迁移调试

我们可以在Xcode中设置启动参数-com.apple.coredata.ubiquity.logLevel 3或者-com.apple.CoreData.SQLDebug 1, 这样在程序运行时,控制台将会打印更多Core Data使用中的信息,包括调用的SQL语句。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容