开始前,先教一些查看沙盒文件的技巧:ready ?let's do it
一、
先通过下面语句获取Documents路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSLog(@"%@",path);
然后打开Finder, Commend + Shift + G,去往上面获取到的路径即可
二、
使用工具simpholders,这是一款很方便的打开模拟器(和越狱后的iOS设备)沙盒路径的工具,非常好用!可以体验一下
三、
真机调试查看沙盒文件,步骤如下:
得到下面的这样一个文件
右键,点击显示包内容
依次点击: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、纯手动迁移: 当自定义的迁移还是不足以满足需求的时候,需要用户自定义版本检测的逻辑和迁移逻辑。
一、轻量级迁移
首先创建模型版本
然后会弹出下面这个对话框,默认的新的模型会在原来的基础上增加一个数字,来标识不同的模型版本(我由于我已经创建了一个,所以就变成DataMigration 2、DataMigration 3了)。这个数字也是可以更改的,你可以按照自己的喜好更改成 v2 或者其他的
这时将当前的模型版本切换成刚刚创建的那个
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
这时给你新创建的数据模型添加实体和属性,然后将原来的属性实体类删除,重新添加新创建的属性实体类(这一步前面的文章有介绍),就可以进行相应的增删改查操作啦!
上面说到对于一些较小的变化,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就可以自动推断出对应的映射模型
二、手动迁移
当迁移足够简单的时候,符合上面十种规范的时候,可以使用轻量迁移全自动完成。但当数据变化更为复杂时,就需要更加灵活的迁移,手动创建mapping model来实现数据迁移。
首先,再创建一个模型版本: 选中 .xcdatamodeld 文件然后选择 Editor,选择 Add Model Version
在新创建的模型版本中做Model的修改,自己随意修改些内容(ps:刚开始可以少修改一点,看看效果先,熟练了再任意发挥),修改完成后,选中.xcdatamodeld文件,然后new file一个新文件:选择Mapping Model
选择 Source data model 和 Destination data model,也就是迁移的旧版和新版数据模型版本
填写文件名
这样Mapping Model就创建好了,你的工程中会出现这么一个文件
补充说明:
每个版本的数据库之间都最好能加上一个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
关系映射:
对于这种关联到外部表的字段,相对于普通字段会复杂一些,我们需要通过右侧的面板来进行配置,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:
V5:
我将School实体删除了,然后在Student中添加了schoolName和schoolAddress来代替原来的School,如下是mapping model中的内容
最后添加了数据验证OK,Student 表中的 studentSchool字段已经被 schoolName和schoolAddress 替代了,同时其他的数据也都没有丢失。
下面来介绍 mapping model 中会用到的几个对象:
$source - 对应着 NSMigrationSourceObjectKey,可以理解为 Source Model 的一个实体对象
$manager - 对应着 NSMigrationManagerKey,它代表的是 NSMigrationManager 对象,正是这个对象在迁移过程中发挥着作用,它管理着源对象和目标对象之间的关联
除了这两个,还有几个不常用的:
$destination – NSMigrationDestinationObjectKey
$entityMapping – NSMigrationEntityMappingKey
$propertyMapping – NSMigrationPropertyMappingKey
$entityPolicy – NSMigrationEntityPolicyKey