CoreData从入门到精通六(模型版本和数据迁移)

开始前,先教一些查看沙盒文件的技巧:ready ?let's do it
一、
先通过下面语句获取Documents路径

NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSLog(@"%@",path);

然后打开Finder, Commend + Shift + G,去往上面获取到的路径即可

二、
使用工具simpholders,这是一款很方便的打开模拟器(和越狱后的iOS设备)沙盒路径的工具,非常好用!可以体验一下

三、
真机调试查看沙盒文件,步骤如下:


屏幕快照 2018-05-16 下午5.22.52.png

屏幕快照 2018-05-16 下午5.27.17.png

得到下面的这样一个文件


屏幕快照 2018-05-16 下午5.39.43.png

右键,点击显示包内容
屏幕快照 2018-05-16 下午5.41.22.png

依次点击:AppData --> Documents里面就有你要查看的文件了,惊不惊喜

请开始你的表演:

前面几篇文章中讲的所有内容,都是在同一个模型版本上进行操作的。但在真实开发中,基本上不会一直停留在一个版本上,因为需求是不断变化的,说不定什么时候就需要往模型里添加新的字段,添加新的模型,甚至是大规模的重构;所以数据的迁移就显得尤为重要了。

CoreData 中,数据迁移本质就是把旧的 SQLite 数据库里的内容,复制到新的 SQLite 数据库里去,让新的数据库作为默认的数据存储。伴随着模型版本的变化,新旧两个数据库的实体结构当然也是不同的。这就是说在迁移过程中必须知道新旧两个数据库的模型对应关系,旧数据库里的数据该怎么复制到新的数据库中。这在 CoreData 中是由 MappingModel 映射模型来决定的。我们所需要做的就是创建 MappingModel 文件,指定好实体不同版本间的映射,CoreData 就会自动帮我们完成数据迁移。当然如果模型版本的变化比较小,CoreData 是可以自动推断出映射模型的。

下面就来详细的介绍一下 CoreData 里常用的几种迁移:

1、轻量级迁移: 最简单的迁移,用户只需要设计几个标记,迁移就会自动完成。比如我们仅仅是添加实体模型和可选属性的时候,这个时候数据层的变动简单到根本不需要你去告诉 Core Data 如何迁移,也就是它会自动迁移,NSPersistentStoreCoordinator 就会自动生成 Mapping Model。如果有更复杂的变化,就需要自己自定义 Mapping Model 了。下文中罗列了适合轻量级迁移的各种情况。

2、手动迁移: 手动迁移需要自己实现 Mapping 部分的工作,有 GUI 的工具可以完成,可以自定义映射规则,你可以做的事情更多。比如如果你想从一个 Model中将特殊的属性单独拉出来创建一个 Model,这个时候手动迁移就是首选。

3、自定义迁移: 如果你需要快速地在你的数据模型上进行相对复杂的改变,那么自定义迁移就是为你准备的。需要代码实现一些对数据的迁移逻辑。这里自定义的实体迁移逻辑需要自定义一个 NSEntityMigrationPolicy 子类来实现迁移逻辑。

4、纯手动迁移: 当自定义的迁移还是不足以满足需求的时候,需要用户自定义版本检测的逻辑和迁移逻辑。

一、轻量级迁移
首先创建模型版本


屏幕快照 2018-05-16 下午5.55.37.png

然后会弹出下面这个对话框,默认的新的模型会在原来的基础上增加一个数字,来标识不同的模型版本(我由于我已经创建了一个,所以就变成DataMigration 2、DataMigration 3了)。这个数字也是可以更改的,你可以按照自己的喜好更改成 v2 或者其他的
屏幕快照 2018-05-16 下午5.59.14.png

这时将当前的模型版本切换成刚刚创建的那个
屏幕快照 2018-05-16 下午6.07.02.png

NSPersistentStoreCoordinator:

persistentStoreCoordinator 调用 addPersistentStoreWithType:configuration:URL:options:error: 添加 persistentStore时,需要将 options 的 NSMigratePersistentStoresAutomaticallyOption 和 NSInferMappingModelAutomaticallyOption 两个key设置为YES,CoreData 才会自动推断。

 _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
        //指定本地的sqlite数据库文件
        NSURL *sqliteURL = [[self documentDirextoryURL] URLByAppendingPathComponent:@"DataMigration.sqlite"];
        NSError *error;
        //为persistentStoreCoordinator指定本地存储的类型,这里指的是sqlite
        NSDictionary *options = @{
                                  NSSQLitePragmasOption : @{@"journal_mode": @"DELETE"},
                                  NSMigratePersistentStoresAutomaticallyOption : @YES,
                                  NSInferMappingModelAutomaticallyOption : @YES
                                  };
        //option进行如上设置后,coredata才会自动推断映射模型
        [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqliteURL options:options error:&error];

这个 NSMigratePersistentStoresAutomaticallyOption 告诉CoreData的 NSPersistentStoreCoordinator如果存储层的 Model 和实际的Model不匹配的话就开始执行迁移。

后面的 NSInferMappingModelAutomaticallyOption 字面意思翻译是:Model的自动推测映射选项,也就是问你要不要打开轻量迁移。MappingModel 就是迁移的映射助手,它指导着每个数据如何向新的Model中迁移。

其实轻量级迁移的本质就是自动推测映射模型,所以上面这里Optional就是打开轻量级迁移的选项。任何迁移都需要 Mapping Model,只是轻量级迁移会由系统根据推测来自动帮你创建一个Mapping Model

这时给你新创建的数据模型添加实体和属性,然后将原来的属性实体类
屏幕快照 2018-05-17 下午3.26.01.png

删除,重新添加新创建的属性实体类(这一步前面的文章有介绍),就可以进行相应的增删改查操作啦!

上面说到对于一些较小的变化,CoreData 是可以自动推断映射模型的,从而帮助我们自动地完成数据迁移。下面列举轻量迁移能够处理的各种情况:

1、删除实体、属性 或者 关系。
2、使用 renamingIdentifier 重新命名实体、属性或者关系。
3、新添加一个 Optional 的属性。
4、新添加一个 Required 属性,但是必须有默认值。
5、把一个 Optional 属性改成带有默认值的 Required属性。
6、把一个 非Option 的属性改成 Optional属性。
7、改变实体结构。
8、新添加父实体,把属性向父类移动或者将父类属性往子类中移。
9、把 对一 关系改成 对多 关系。
10、改变关系,从 non-ordered to-many到ordered to-many。

上面说到的 renaming identifier 可以在 Model Inspector 进行设置,对不同数据模型版本(相对应的实体/属性)设置相同的Renaming ID,CoreData就可以自动推断出对应的映射模型

说到这,为了能直观的看出变化,文章开头的查看沙盒文件的方法就可以用上了,首先你得下一个sqlite软件(如下),就可以查看数据库的内容了,哈哈
屏幕快照 2018-05-16 下午6.28.27.png

二、手动迁移

当迁移足够简单的时候,符合上面十种规范的时候,可以使用轻量迁移全自动完成。但当数据变化更为复杂时,就需要更加灵活的迁移,手动创建mapping model来实现数据迁移。

首先,再创建一个模型版本: 选中 .xcdatamodeld 文件然后选择 Editor,选择 Add Model Version

在新创建的模型版本中做Model的修改,自己随意修改些内容(ps:刚开始可以少修改一点,看看效果先,熟练了再任意发挥),修改完成后,选中.xcdatamodeld文件,然后new file一个新文件:
屏幕快照 2018-05-17 下午1.58.51.png

选择Mapping Model


屏幕快照 2018-05-17 下午2.03.46.png

选择 Source data model 和 Destination data model,也就是迁移的旧版和新版数据模型版本


屏幕快照 2018-05-17 下午2.05.19.png

屏幕快照 2018-05-17 下午2.08.01.png

填写文件名


屏幕快照 2018-05-17 下午2.08.59.png

这样Mapping Model就创建好了,你的工程中会出现这么一个文件


屏幕快照 2018-05-17 下午2.20.13.png

补充说明:
每个版本的数据库之间都最好能加上一个Mapping文件,这样从低版本的数据库升级上来,可以保证每个版本都不会出错,都不会导致用户升级之后就出现闪退的问题。试想,如果用户实在V3的老版本上,由于appstore的更新规则,每次更新都直接更新到最新,那么用户更新之后就会直接到V5,如果缺少了中间的V3ToV4,V4ToV5,中的任意一个,那么V3的用户都无法升级到V5上来,都会闪退。所以这里就看出了每个版本之间都要加上Mapping文件的重要性了。这样任意低版本的用户,任何时刻都可以通过Mapping文件,随意升级到最新版,而且不会闪退了!

然后我们来了解一下 mapping model 的用法,主要分为属性映射Attributes Mappings和关系映射Relationship Mapping

属性映射:
当映射模型创建出来的时候,Xcode 已经对新旧版本进行了推测,已经进行了一些属性的映射,所以可以在这个Xcode推测的版本上进行属性的映射。

新创建的属性映射操作界面包含了两部分,上面是属性映射Attributes Mappings,下面是关系映射Relationship Mappings。

在 Attributes 中配置属性的映射,其中 Destination Attribute 目标属性是指新的 Model 中的 Attribute,Value Expression 指的是这项数据从哪里来,在其中可以使用 $source 来作为数据源实例的引用($source.name)。

在左边 ENTITY MAPPINGS 中显示的是实体映射的名称,选择一个实体映射可以在右边编辑窗口修改它的名称,命名规范为:源实体名To新实体名,比如 StudentToStudent,表示从迁移源中的 Student 到新的 Model 中的 Student 的迁移实体映射。

右边窗口可以在 Source 中选择迁移源的 Model,选择后 Xcode 回自动根据属性名来进行一些匹配。

还可以选中左边的 ENTITY MAPPINGS,然后在右边的 Filter Predicate 中编辑 predicate 条件,比如 name != nil


屏幕快照 2018-05-17 下午2.31.19.png

关系映射:


屏幕快照 2018-05-17 下午2.56.57.png

对于这种关联到外部表的字段,相对于普通字段会复杂一些,我们需要通过右侧的面板来进行配置,Name 代表 RelationShip 的字段名;Key Path 代表这个字段对应的源对象上的字段,对于 studentCourses 来说就是 $source. studentCourses;然后是 Mapping Name,它代表这个 RelationShip 所关联的外部表的 Entity Mapping,对于 studentCourses 来说就是 Course 的 Entity Mapping 也就是 CourseToCourse。配置好这些后,Xcode 会生成一段长长的 Value Expression 表达式:

FUNCTION($manager, "destinationInstancesForEntityMappingNamed:sourceInstances:" , "CourseToCourse", $source.studentCourses)

Course里的courseStudents也是同样的配置方法
原本就有的关系映射就不用配置了,默认都给你配好了,新增的才需要做配置

所有字段都配置完后,就可以把模型版本切换到最新添加的版本, 然后运行程序。程序在运行时发现当前的版本数据模型和本地存储的数据库版本(原版本)不一致,就会自动从 bundle 里寻找两个版本间对应的 Mapping Model,依据自定义的 Mapping Model,数据就会自动迁移完成

下面是我的例子:
先看一下我的两个模型版本(V4和V5)中的内容:
V4:


屏幕快照 2018-05-17 下午2.27.26.png

V5:


屏幕快照 2018-05-17 下午2.27.44.png

我将School实体删除了,然后在Student中添加了schoolName和schoolAddress来代替原来的School,如下是mapping model中的内容


屏幕快照 2018-05-17 下午3.12.34.png

最后添加了数据验证OK,Student 表中的 studentSchool字段已经被 schoolName和schoolAddress 替代了,同时其他的数据也都没有丢失。

下面来介绍 mapping model 中会用到的几个对象:

$source - 对应着 NSMigrationSourceObjectKey,可以理解为 Source Model 的一个实体对象
$manager - 对应着 NSMigrationManagerKey,它代表的是 NSMigrationManager 对象,正是这个对象在迁移过程中发挥着作用,它管理着源对象和目标对象之间的关联
除了这两个,还有几个不常用的:

$destination – NSMigrationDestinationObjectKey
$entityMapping – NSMigrationEntityMappingKey
$propertyMapping – NSMigrationPropertyMappingKey
$entityPolicy – NSMigrationEntityPolicyKey

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

推荐阅读更多精彩内容