天天看点

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

本节书摘来自华章出版社《core data应用开发实践指南》一书中的第3章,第3.5节,作者 (美)tim roadley,更多章节内容可以访问云栖社区“华章计算机”公众号查看

除了通过nspersistentstorecoordinator来迁移存储区之外,还可以采用迁移管理器来做。迁移管理器可以使开发者全权掌控迁移过程中创建的文件,从而令他们能够按自己的方式来灵活处理迁移中的各种问题。使用迁移管理器的一个好处就是可以向用户报告迁移进度,使用户知道应用程序哪次会启动得比较慢一些,所以需要耐心等待。虽说迁移过程理应执行得非常快才对,但当数据库比较大、变动比较复杂时,迁移过程就需要耗费一定的时间了。为了使用户界面保持流畅,迁移过程必须在后台线程里执行。只有这样做,用户界面才能反应灵敏,并能把最新动态提供给用户。实现数据迁移的难点在于如何防止用户在迁移过程中操作应用程序。由于此时数据尚未准备好,所以我们必须做这个限制,否则用户就会对着黑屏不知所措。

请按下列步骤修改grocery dude,以便配置migration 视图控制器(view controller):

选定main.storyboard。

向故事板(storyboard)中拖放一个新的view controller,将它摆在现有的navigation 控制器上方。

向这个新的视图控制器中拖放label与progress view控件。

把progress view放在视图控制器正中,并将label放在progress view上方。

按图3-10拓宽label与progress view控件,使其宽度与视图控制器相符。

把label的文本改为migration progress 0%,并将其居中(centered),如图3-10左侧所示。

把progress view的进度(progress)设为0。

选中视图控制器,然后在identity inspector界面(可以按“option++3”组合键调出该界面)中把视图控制器的storyboard id设为migration。

点击editor>resolve auto layout issues>reset to suggested constraints in view controller菜单项。最终结果如图3-10所示。

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

由于migration 视图控制器里的uilabel控件及uiprogressview控件需要在迁移过程中更新,所以我们需要一种能在代码中使用这两个控件的方式。为此,我们新建uiviewcontroller的子类,并将其命名为migrationvc。

请按下列步骤修改grocery dude,以便将migrationvc类添加到新的组中:

在现有的grocery dude 组上面右击鼠标,选择new group。

把新组的名字改为grocery dude view controllers。

选中grocery dude view controllers 组。

点击file>new>file...菜单项。

新建ios>cocoa touch>objective-c class,并点击next按钮。

把subclass of设为uiviewcontroller,并把class名称设为migrationvc,然后点击next按钮。

确保targets中的“grocery dude”处于勾选状态,然后点击create,在grocery dude项目的文件夹中创建这个类。

选中main.storyboard。

将migration 视图控制器选中,在identity inspector界面(可按“option++3”组合键调出该界面)里把custom class设为migrationvc。该选项和刚才设置的storyboard id都位于同一界面中。

点击view>assistant editor>show assistant editor菜单项(或按“option++return”组合键),把assistant editor界面显示出来。

此时assistant editor界面中应该就会自动显示出migrationvc.h文件了。图3-11右上角是该界面的样貌。如果显示的不是这个文件,那可以先把migration 视图控制器选中,然后点击界面上方的manual或automatic,再选择migrationvc.h。

点击control键并按住鼠标左键不放,从显示迁移进度的label控件开始沿直线拖到migrationvc.h代码的@end上方。松开鼠标左键之后,会弹出一个对话框,该对话框中列出了类型为uilabel的新特性,我们把该特性的name设为label,并确保storage是strong,然后点击connect。配置好的各选项如图3-11所示。

按第12步所讲的操作方式,把progress view(进度视图)同uiprogressview类型的特性链接起来,并将该特性命名为progressview。

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

为了向用户报告迁移进度,我们需要在coredatahelper.h文件中声明指向migration 视图控制器的指针。

请按下列步骤修改grocery dude,以添加新特性:

点击view>standard editor>show standard editor菜单项或按“+return”组合键,把standard editor界面显示出来。

把#import "migrationvc.h"添加到coredatahelper.h文件顶部。

在coredatahelper.h文件的现有特性下方添加@property(nonatomic, retain)migrationvc*migrationvc;。

如果采用手动方式迁移数据,那就得在每次启动应用程序时判断数据是否需要迁移。为了做出该判断,我们需要知道存储区的url,以便检查系统里是不是有这个存储区。如果有的话,那还要把存储区里的“模型元数据”(model metadata)与新的模型相比较,并根据比较的结果来判断新模型是否与现有的存储区相兼容。假如不兼容,那就要迁移数据了。把刚才说的这段逻辑写成代码,就得到了程序清单3-5中的ismigrationnecessary-forstore方法。

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

请按下列步骤修改grocery dude,以便实现新的migration manager 部分:

将程序清单3-5中的代码添加到coredatahelper.m文件底部,并将其放在@end语句之前。

假如已经确定要迁移数据,那么接下来就该执行迁移了。此过程分为三步,程序清单3-6中的注释写明了这三个步骤。

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据
《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据
《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

step 1(第一步) 用于收集执行数据迁移所需的信息,这些信息分别是:

源模型,也就是通过nspersistentstorecoordinator的metadatafor-persistentstoreoftype方法从持久化存储区里获取到的元数据。

目标模型,也就是_model实例变量。

映射模型,该模型由系统自动决定,开发者只需把nil当做mappingmodelfrom-bundles:forsourcemodel:destinationmodel:方法的第一个参数,并把源模型和目标模型也一并传过去即可。

step 2(第二步) 就是实际的迁移过程。我们先用源模型与目标模型创建nsmigra-tionmanager实例,然后在调用migratestorefromurl之前,还需把目标存储区准备好。该目标存储区只是个为迁移而设的临时存储区。

step 3(第三步) 只有在顺利完成迁移时才会触发。replacestore方法用于在迁移完成后清理旧的存储区。执行完第二步之后,目标位置上就会出现一份新的存储区了,但是,我们还必须把这个迁移过来的新存储区放回到原来的位置上,并且要把它的文件名起得和旧存储区一样,唯有如此,core data才能使用这个新存储区。为了使用新迁移好的存储区,我们需要把旧存储区删掉,并将新存储区放到旧存储区的位置上。当开发自己的项目时,也可以在删除旧存储区之前先把它备份到某处。是否需要备份,由你自己决定,如果真要备份,那可能得稍微修改一下replacestore方法。备份旧存储区会导致应用程序在迁移过程中对存储量的需求翻倍。

当迁移进度有变化时,系统会调用observevalueforkeypath方法,而我们可以通过该方法把目前的迁移进度告知用户。migrationmanager的migrationprogress特性一旦改变,我们就可通过该方法来更新migrationvc。

程序清单3-7列出了observevalueforkeypath及replacestore方法的代码。

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据
《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

请按下列步骤修改grocery dude,以继续实现migration manager 部分:

把程序清单3-7里的代码添加到coredatahelper.m文件migration manager部分的底部,并放在@end上方,然后按同样方式把程序清单3-6里的代码也加进去。

为了在后台通过migrationmanager来迁移数据,我们需要使用程序清单3-8中的方法。

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据
《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

performbackgroundmanagedmigrationforstore方法用故事板标识符来实例化视图控制器,并把它展示给用户。用户的操作由这个视图来接受,而我们则可以开始迁移数据了。migratestore方法会在后台线程中执行。等迁移完数据,我们就可像平常那样通过_coordinator来添加存储区,并把显示迁移进度所用的view关闭,然后,应用程序就可以照常往下运行了。

请按下列步骤修改grocery dude,以继续实现migration manager部分:

把程序清单3-8里的代码添加到coredatahelper.m文件migration manager 部分的底部,并放在@end语句上方。

检测是否需要执行数据迁移的最佳时机应该是在把存储区添加到_coordinator前的那一刻。为了安排好这项检测,我们需要修改coredatahelper.m文件中的loadstore方法。如果真的要迁移,那么迁移操作就会在此刻触发。相关代码如程序清单3-9所示。

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据
《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

请按下列步骤修改grocery dude,以完成本节范例:

修改coredatahelper.m文件中的loadstore方法,用程序清单3-9里的代码把原有代码替换掉。

基于model 3,添加名为model 4的模型版本。

选定model 4.xcdatamodel。

删除amount实体。

新增名为unit的实体,并添加名为name的字符串类型属性。

根据unit实体来创建nsmanagedobject子类。在保存类文件的这一步里,别忘了勾选targets中的“grocery dude”。

将model 4设为当前模型。

以model 3为源模型,以model 4为目标模型,新建映射模型。在保存映射模型文件的这一步里,别忘了勾选targets中的“grocery dude”,然后,把这个模型存为model3tomodel4。

选定model3tomodel4.xcmappingmodel。

选定entities mappings中的unit。

将unit实体的source设为amount,并给名为name的destination 属性设定value expression,将这个value expression写成$source.xyz。此时entities mappings中的unit实体应该会自动改名为amounttounit,如图3-12所示。

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

现在基本上已经可以开始执行迁移了,不过demo方法里的获取请求仍然在使用旧的amount实体。

请按下列步骤修改grocery dude,使demo方法不再获取amount实体,而是改为获取unit实体:

把appdelegate.m文件顶部的#import "amount.h"替换成#import "unit.h"。

2.修改appdelegate.m文件的demo方法,用程序清单3-10中的代码替换原有代码。新的代码只从持久化存储区里获取50个unit对象。

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

迁移管理器终于实现好了!运行应用程序,仔细观察设备屏幕!你眼前会出现migration progress界面,它会显示数据的迁移进度。同时,这个进度也会出现在控制台里。

请用第2章讲过的办法来查看grocery-dude.sqlite文件中zunit表的内容。正常的结果如图3-14所示。假如你已把默认的日志记录模式关闭,但stores目录里却还有-wal文件,那么请点击product>clean菜单项并重新运行应用程序,然后再次查看sqlite文件。

《Core Data应用开发实践指南》一3.5 通过迁移管理器来迁移数据

操作结果要是和图3-14一样的话,那你真的太棒了,因为你已把三种模型迁移方式全部实现出来了!本书接下来的内容要使用轻量级迁移方式,所以现在必须重新启用它。

请按下列步骤修改grocery dude,以重新启用轻量级迁移方式:

修改coredatahelper.m文件的loadstore方法,把nsinfermappingmodel-automaticallyoption选项改为@yes。

修改coredatahelper.m文件的loadstore方法,把usemigrationmanager设为no。

删除appdelegate.m文件demo方法中的所有代码。

旧的映射模型以及根据旧实体所创建出来的nsmanagedobject子类现在都已经没有用处了。虽说也可以把它们删掉,但为了便于日后查阅,我们还是将其留在项目之中吧。