Core Data 版本迁移攻略(上)

这一部分包含了迁移的简介,回答了迁移是什么,什么时候要迁移的问题,写了轻量级迁移 和 基于 MappingModel 的手动迁移。

纯手动迁移 和 渐进式迁移 放在后面。


0. 前言概述

发布应用新版本的时候经常会遇到这样头痛的问题,新版本的 Core Data 的数据模型要增加新的属性,当然你要对 Core Data 的 Data Model 做出修改,Core Data 的迁移就是为了解决这样的问题。

关于迁移渠道呢?Migration 会使用 映射 或 匹配,从而使得旧的 Data Model 升级为新的 Data Model。

简单来说,任何时候你的应用程序都会有新的变化,然而并不是每一次变更都不会更新 Data Model,当它改变时,你需要提供新的 Data Model,并且提供迁移途径。

1. Core Data 迁移概述

Core Data 进行迁移,首先要把握时机。

那么在什么时机发生迁移呢?在 Core Data 在程序中初始化的时候,会创建我那篇文章的文章中提到的Core Data Stack,反正不管怎么样初始化,它初始化一定会把 Sotre 添加进存储化存储助手也就是NSPersistentStoreCoordinator,然而迁移就是在添加这个之前发生的。

然后 Core Data 已经把握好了这个时机,他就要考虑现在我要不要迁移呢?这个问题。只有一种情况下会迁移,Store 的 Model 版本跟当前的存储助手配置的 Model 版本不相符的时候。当然话说回来不相符之后你还得启用迁移开关才行,开关没开的话就报错。

那么如果开始执行迁移:首先去分析原先的 Data Model 和你现在的目标 Data Model,使用这两个 Model 去创建 迁移映射模型(Migration mapping model),这个映射模型用来把旧的Model里面的数据转移到新的Model里面。

迁移的三个步骤:

  1. Core Data 把旧的 Store 里面的全部数据拷贝到新的一个 Store 中。
  2. Core Data 根据关系映射把所有的数据 Object 关联起来。
  3. 会在目标 Model 中加上数据验证。所以在拷贝过程中 Core Data 禁用了数据验证。

另外,只有在迁移没有任何报错的情况下,迁移执行完成,旧的 Store 才会被删除。

1.1 迁移类型

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

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

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

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

2. 轻量迁移

接下来的步骤走一遍简单的轻量迁移:

创建一个新的数据版本:在开始做改动之前,就是你还没有修改你的第一版本的时候,选中 xcdatamodeld 文件然后选择 Editor,选择 Add Model Version,给新版本起一个名字,最简单的--在原名字后加个 v2。

可以在文件查看窗口(File Inspector pane)对 xcdatamodeld 文件的 Version 进行查看和更改。现在切换到刚刚创建的新的版本。

在新的版本中选择一个 Entity,给这个实体上添加一个新的 Property,名字按需求来,类型选择 Transformable。

点击新的属性,在右边的编辑窗口的第三个数据模型查看器(Data Model Inspector),在上面的 Attribute Type 下面有一个Name,输入:YourProjectName.ImageTransformer(按照图片类型举例)。

然后去这个实体的 .swift 中添加属性:

@NSManaged var image: UIImage!

这个时候如果运行了程序,一定会报错吧。因为前言部分说了,如果 CoreData 发现你实际存在的 Model 和你程序里面定义的Model不一致的话,它就会表示这两个不匹配,然后抛错。所以先激活轻量迁移。

激活轻量迁移

激活的时候你需要在初始化的时候加两个标记,在 Core Data Stack 中加入一个属性:

var options: NSDictionary?

修改初始化 init 方法:

init(modelName:String, storeName:String, options: NSDictionary? = nil) {
  self.modelName = modelName
  self.storeName = storeName
  self.options = options
}

虽然修改了方法的签名,但是option设置为nil,意思就是旧的方法签名仍旧可用,不会造成程序其他地方需要修改。

store = coordinator.addPersistentStoreWithType( 
  NSSQLiteStoreType, 
  configuration: nil, 
  URL: storeURL, 
  options: self.options)

在创建 Core Data Stack 的类中,修改初始化的代码:

lazy var stack : CoreDataStack = CoreDataStack(
  modelName:"YourModelName",
  storeName:"YourStoreName",
  options: [NSMigratePersistentStoresAutomaticallyOption: true,
          NSInferMappingModelAutomaticallyOption: true])

开关就在这了

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

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

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

下面列举 轻量迁移 能够处理的各种情况:

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

3. 手动迁移

当迁移足够简单的时候,符合上面十种规范的时候,可以使用轻量迁移全自动完成。但是来了,但是如果你想要更加灵活的迁移,手动迁移第一种是使用 Mapping Model 去实现迁移,功能更强大,更加灵活。

创建一个新的 Data Version:

选中 xcdatamodeld 文件然后选择 Editor,选择 Add Model Version,给新版本起一个名字,可以在原名字后加 v3。

然后在新的 v3 版本中做 Model 的修改,确认完成所有修改之后,创建 New File,选择 iOSCoreData Mapping Model,然后选择 Mapping Model Source Data Model,这个选 你创建新的 Data Version 的之前的那本版本,然后下一步选择Target Model Target Data Model,这个选择刚刚的那个 v3 版本。

创建的这个文件的名字按照约定成俗,前面是Model的名字,加上MappingModel,后面加上旧的Model版本和新的Model版本,ModelNameMappingModel_v2_to_v3

属性映射

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

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

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

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

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

还可以选中左边的 Enitty Mapping,然后在右边的 Filter Predicate 中编辑 predicate 条件,比如 name != nil 这一类。

关系映射

关系映射我理解是会比属性映射比较难理解一些。这里是第一次出现。

你知道,跨实体的迁移时有会发生。举个栗子,比如我们上一个项目中,主实体是会议,会议有一个账单数组属性,后来要单独拉出来做成一个账单的 Model,这个时候迁移的时候要看用户有多少账单,我就要迁移中创建多少个账单 Model,并且会议和账单的关系是一对多,全部关联好,这个实体之间关系的迁移就在这里。

在 xcmappingmodel 的下半编辑区域,是属于 Relationship Mapping 的编辑区域,可以在这里创建关系映射。

右边的属性编辑区域中,可以选择属性,选择 Source Fetch 的方式(Auto Generate Value Expression 是自动生成),填写 keyPath,还有 MappingName。

FUNCTION(
$manager,
"destinationInstancesForEntityMappingNamed:
        sourceInstances:" , 
"NoteToNote", $source)

编辑好的映射的 Value Expression 就像上面这个一样。

深入一下这个 $manager,它指向一个 NSMigrationManager 对象,这个对象在迁移过程中发挥作用,它处理着迁移的过程,Migration manager 迁移管理员 管理着源对象和目标对象的关联。之前有 $source,这里又有$manager,Core Data还提供了别的一些对象可以在 xcmappingmodel 中使用

NSMigrationManagerKey: $manager

NSMigrationSourceObjectKey: $source

NSMigrationDestinationObjectKey: $destination

NSMigrationEntityMappingKey: $entityMapping

NSMigrationPropertyMappingKey: $propertyMapping

NSMigrationEntityPolicyKey: $entityPolicy

NSEntityMigrationPolicy 自定义迁移的写在后面。

到这里梳理一下:基于 Mapping Model 的手动迁移需要做什么?

  1. 创建新的 Data Version 并作好新数据版本的修改工作
  2. 创建 xcdatamodeld 并配置好 属性映射 和 关系映射
  3. 配置初始化

第三步就是要改一行代码,修改 Core Data Stack:

options: [NSMigratePersistentStoresAutomaticallyOption: true,
          NSInferMappingModelAutomaticallyOption: false]

给 NSInferMappingModelAutomaticallyOption 设置为 False,持久化存储助手就会使用 MappingModel 去迁移存储层。

深入:那这里发生了什么?Core Data 发现不使用轻量级自动迁移,因为是 false 嘛,然后它就会在 bundle 里面去查找它需要的 mapping model,因为它知道当前的Model是从哪一版迁移到哪一版的,所以它很明确bundle 中的哪一个文件是它当前需要的。

你可能感兴趣的:(Core Data 版本迁移攻略(上))