Core Data 迁移与版本管理
大家在学习和使用Core Data过程中,第一次进行版本迁移的经历一定是记忆犹新,至少我是这样的,XD。弄的不好,就会搞出一些由于迁移过程中数据模型出错导致的Crash。这里总结了一下Core Data版本迁移过程中的经验,希望对大家有用。
写在前面
关于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语句。
Core Data 迁移与版本管理的更多相关文章
- 手把手教你从Core Data迁移到Realm
来源:一缕殇流化隐半边冰霜 (@halfrost ) 链接:http://www.jianshu.com/p/d79b2b1bfa72 前言 看了这篇文章的标题,也许有些人还不知道Realm是什么,那 ...
- 手把手教你从 Core Data 迁移到 Realm
前言 看了这篇文章的标题,也许有些人还不知道Realm是什么,那么我先简单介绍一下这个新生的数据库.号称是用来替代SQLite 和 Core Data的.Realm有以下优点: 使用方便 Realm并 ...
- 自定义 Core Data 迁移
本文转载至 http://objccn.io/issue-4-7/ 感谢本文作者 朱宏旭 的不啬分享 自定义 Core Data 迁移似乎是一个不太起眼的话题.苹果在这方面只提供了很少的文档,若是初次 ...
- Core Data 版本号迁移经验总结
大家在学习和使用Core Data过程中,第一次进行版本号迁移的经历一定是记忆犹新,至少我是这种,XD.弄的不好,就会搞出一些因为迁移过程中数据模型出错导致的Crash.这里总结了一下Core Dat ...
- 《驾驭Core Data》 第一章 Core Data概述
<驾驭Core Data>系列教程综合了<Core Data for iOS>,<Learning Core Data for iOS>,<Core Data ...
- Core Data 版本数据迁移
Core Data版本迁移基础 通常,在使用Core Data的iOS App上,不同版本上的数据模型变更引发的数据迁移都是由Core Data来负责完成的.这种数据迁移模式称为Lightweight ...
- 3.3. 轻量级的迁移方式(Core Data 应用程序实践指南)
持久化存储协调器会试着用新版的模板打开原来的持久化存储区,但是那是旧的模板,旧的格式,当然会出错.现在要做的就是迁移现有的持久化数据区,以便跟新模型匹配. 怎么进行迁移呢? 在什么时候进行迁移? 在向 ...
- 前言(Core Data 应用开发实践指南)
Core Data 并不是数据库,它其实是一个拥有多种功能的框架.其中,有个功能是把程序与数据库之间的交互过程自动化,不用再编写SQL代码,改用Objective-C对象来实现. Core Data ...
- 工程日记之HelloSlide(3):如何使用Core Data数据库,以及和sqlite之间的对应关系
Core Data 和 SQLite 是什么关系 core data是对sqlite的封装,因为sqlite是c语言的api,然而有人也需要obj-c的api,所以有了core data ,另外,co ...
随机推荐
- 无插件用Terminal/TotalTerminal的开当前finder位置
从win7开始,在资源管理器内按住shift再右键,是可以选择“在当前位置打开命令行”的,相当有用,这个命令在mac下是如何实现的呢? 前提:我讲的是TotalTerminal下的方案,原生的term ...
- application/x-www-form-urlencoded 的contentType,POST数据内容过大,导致tomcat的request取不到参数
如题, 可通过设置tomcat的connector的参数 server.xml中的connector中加上属性 maxPostSize="20971520" maxPostSize ...
- Spring-----配置及对象初始化(1)
一,配置文件进行Spring初始化 1,配置文件编写 <?xml version="1.0" encoding="utf-8" ?> <con ...
- CM和CDH的安装-进阶完成
安装Cloudera Manager Server 和Agent 1.在cdh1解压cloudera-manager-el6-cm5.9.0_x86_64.tar.gz(cdh1节点)tar -zcv ...
- PHP代码审计笔记--URL跳转漏洞
0x01 url任意跳转 未做任何限制,传入任何网址即可进行跳转. 漏洞示例代码: <?php $redirect_url = $_GET['url']; header("Locati ...
- Splash images_enabled 属性
images_enabled属性用于设置加载页面时是否加载图片,如下,禁止之后,返回的页面截图就不会带有任何图片,加载速度也会快很多 function main(splash, args) splas ...
- Ora2Pg的安装和使用
1. 安装DBI,DBD::Oracle DBI只是个抽象层,要实现支持不同的数据库,则需要在DBI之下,编写针对不同数据库的驱动.对MySql来说,有DBD::Mysql, 而对ORACLE来说,则 ...
- drawCall_01
在屏幕上渲染物体,引擎需要发出一个绘制调用来访问图形API(iOS系统中为OpenGL ES).每个绘制调用需要进行大量的工作来访问图形API,从而导致了CPU方面显著的性能开销. Unity在运 ...
- 如何构建日均千万PV Web站点 (一)
其实大多数互联网网站起初的网站架构都是(Linux+Apache+MySQL+PHP). 不过随着时代的发展,科技的进步.互联网进入寻常百姓家的生活.所谓的用户的需求,铸就了一个个互联网大牛: htt ...
- 说说C与汇编之间的互相联系(转)
在嵌入式系统开发中,目前使用的主要编程语言是C和汇编,C++已经有相应的编译器,但是现在使用还是比较少的.在稍大规模的嵌入式软件中,例如含有OS,大部分的代码都是用C编写的,主要是因为C语言的结构比较 ...