【优化篇】coreData数据迁移
coreData数据模型在迭代中修改了,如何保证新新版本的数据兼容旧版本的数据,可能你需要进行coreData的数据迁移。
前面有有写过一篇关于coredata简单使用的教程【进阶篇】iOS coreData简单使用教程
说起coredata的数据迁移,解决不当的后果是 闪退
,这种问题出现的原因是在版本迭代的过程中,对数据模型进行了修改,而老版本的用户在进行App升级的时候,数据结构无法对应,导致App在一加载的时候就会直接闪退,无法进入App,这对用户体验来说是非常致命的。
Method one
下面看一个crash log
XXXXX[459:240351] CoreData: error: -addPersistentStoreWithType:SQLite configuration:(null) URL:file:///var/mobile/Containers/Data/Application/335A17E1-5F73-4CCB-BF2A-BA216CAD9810/Documents/Model.sqlite options:{
NSInferMappingModelAutomaticallyOption = 1;
NSMigratePersistentStoresAutomaticallyOption = 1;
} ... returned error Error Domain=NSCocoaErrorDomain Code=134130 "未能完成操作。(“Cocoa”错误 134130。)" UserInfo=0x18b26070 {URL=file:///var/mobile/Containers/Data/Application/335A17E1-5F73-4CCB-BF2A-BA216CAD9810/Documents/Model.sqlite, metadata={
NSPersistenceFrameworkVersion = 519;
NSStoreModelVersionHashes = {
HSDAccountInfo = <75d8e58f c7645625 e4b50374 2c971ccc 89681907 c866e0c1 51a76840 105af4b7>;
HSDBaseEtt = <8300e6ad 52e70f8d b6f36112 4746b69f c927678b ee31688c 58266961 c88baf3f>;
HSDInvestDetailInfo = ;
HSDInvestInfo = <8b1f6048 2bfd9345 bf39aa1b 73867b56 3bcd5880 be28681f e3ffcb83 c4cc09ef>;
HSDLoanDetailInfo = <67a48388 0aa3cee7 31113c81 9599a537 cee77239 7a12ba48 ff18c599 3ca1c6e5>;
HSDLoanListInfo = ;
HSDNewsInfo = ;
HSDReturnAmtInfo = <2c0ede3a 48523624 42a1b64e 85f75755 a7594f01 8c10c62d 0fb71f99 5fb94535>;
HSDTenderInfo = ;
HSDTopImageInfo = <04839559 19fbcb38 2dc35bb1 b6202cf2 58d66a0d cf48455c 1b321f8a c34ace17>;
HSDTopUpInfo = <0f56dc09 ac6d0793 543fe989 88814efa 2d159436 143d8cdf 840b86aa bb0e6373>;
HSDWithdrawDepositInfo = <0ccb845a 94dc8ab4 accaaa21 a15b8b94 ac59ef5e 622825d2 3ee6830d 705f9ed0>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "BBD0C7D5-B836-4BB9-9C51-B0874103AF99";
"_NSAutoVacuumLevel" = 2;
}, reason=Can't find model for source store} with userInfo dictionary {
URL = "file:///var/mobile/Containers/Data/Application/335A17E1-5F73-4CCB-BF2A-BA216CAD9810/Documents/Model.sqlite";
metadata = {
NSPersistenceFrameworkVersion = 519;
NSStoreModelVersionHashes = {
HSDAccountInfo = <75d8e58f c7645625 e4b50374 2c971ccc 89681907 c866e0c1 51a76840 105af4b7>;
HSDBaseEtt = <8300e6ad 52e70f8d b6f36112 4746b69f c927678b ee31688c 58266961 c88baf3f>;
HSDInvestDetailInfo = ;
HSDInvestInfo = <8b1f6048 2bfd9345 bf39aa1b 73867b56 3bcd5880 be28681f e3ffcb83 c4cc09ef>;
HSDLoanDetailInfo = <67a48388 0aa3cee7 31113c81 9599a537 cee77239 7a12ba48 ff18c599 3ca1c6e5>;
HSDLoanListInfo = ;
HSDNewsInfo = ;
HSDReturnAmtInfo = <2c0ede3a 48523624 42a1b64e 85f75755 a7594f01 8c10c62d 0fb71f99 5fb94535>;
HSDTenderInfo = ;
HSDTopImageInfo = <04839559 19fbcb38 2dc35bb1 b6202cf2 58d66a0d cf48455c 1b321f8a c34ace17>;
HSDTopUpInfo = <0f56dc09 ac6d0793 543fe989 88814efa 2d159436 143d8cdf 840b86aa bb0e6373>;
HSDWithdrawDepositInfo = <0ccb845a 94dc8ab4 accaaa21 a15b8b94 ac59ef5e 622825d2 3ee6830d 705f9ed0>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
""
);
NSStoreType = SQLite;
NSStoreUUID = "BBD0C7D5-B836-4BB9-9C51-B0874103AF99";
"_NSAutoVacuumLevel" = 2;
};
reason = "Can't find model for source store";
}
以上在在程序启动的时候打印的崩溃信息,可以看到说是没有找到对应的实体文件,在iOS9.0以下,如果对数据模型做了改动,仅仅在persistentStoreCoordinator
中添加以下代码,是没有用的
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
经多次验证,这中迁移方式,在iOS9.0以下仍然会存在闪退,所有需要更加高级的迁移方式,并且有些地方需要注意:
1、不要动原来的Model.xcdatamodeld
文件,不能直接在这个文件中添加行的表,或者添加字段。
2、模型版本控制,保留原来的模型版本,在原来模型版本的基础上创建一个新的映射模型来做改动,选中Model.xcdatamodeld
文件,Editor->Add Model Version,选择一个基础模型,命名为Model_to_Model2
,意味着这是一个以Model为基础改动的模型。
3、创建好了之后,我们会得到Model_to_Model2.xcmappingmodel
文件,点击它查看,其实它会和Model里面的内容一摸一样,接下来,我们将需要新增的表,或者字段,添加在这个映射模型中就行了,这样就完成了一次模型映射的数据迁移了。
Method tow
这个方法粗暴简单,不到特殊的情况(找不出解决办法的时候),推荐使用这个,数据迁移的报错都是因为数据模型文件不符的原因,我们在程序第一次启动的时候,将原来的旧的文件删除,在重新创建新的就行了。
#pragma mark -删除文件
- (void)deleteFileAtPath:(NSString *)filename
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0];
NSString *delelteFilePath = [documentsDirectory stringByAppendingPathComponent:filename];
NSError *error;
if ([fileManager removeItemAtPath:delelteFilePath error:&error] != YES)
NSLog(@"Unable to delete file: %@", [error localizedDescription]);
}
.sqlite的文件在Document
文件目录下,这个方法只需要传入文件的名字,就可以删除掉了。
注意:必须在程序启动时,在未调用coraData的上下文对象前调用这个方法来删除原来的文件,因为coredata的持久化对象使用的是懒加载方法,如果没有调用,就不会闪退,可以尽量将这个方法放在didFinishLaunchingWithOptions
方法的靠前位置,删除了原来的数据库文件,原来版本所有的缓存数据将不复存在
(怪我咯~)
Method three
渐进式迁移
关于这种方法,可以先来看一下当初我做数据迁移时候写的感想:
这次是数据迁移并没有成功,最后仍然是通过将原来的数据库文件删除的方式得到的解决。归纳其中的原因很可能是我动了原来的数据模型,然后对Coredata数据迁移的也并不是很了解,目前已经不知道在其中的某个改动过程出现了什么问题在使用coredata的过程中,非常需要注意的事情是,一旦提交了一个版本后,绝对不能再对原来的数据模型进行复制的标结构操作(如添加表,多表迁移改动数据)这样的操作必须要先添加一个Model version,然后再去在这个modek version中进行复制的表结构操作,如果是轻微的改动,使用轻量级的数据迁移即可,如果是复杂的改动,苹果官网的文档上推荐使用手动指引数据迁移,包括非常重要的多版本控制。
地址:苹果官网对数据迁移的文档描述
这个方法是一个渐进式迁移 (Progressive Migrations)
与其为每个之前的数据模型到最新的模型间都建立映射模型,还不如在每两个连续的数据模型之间创建映射模型。以前面的例子来说,版本 1 和版本 2 之间需要一个映射模型,版本 2 和版本 3 之间需要一个映射模型。这样就可以从版本 1 迁移到版本 2 再迁移到版本 3。显然,使用这种迁移的方式时,若用户在较老的版本上迁移过程就会比较慢,但它能节省开发时间并保证健壮性,因为你只需要确保从之前一个模型到新模型的迁移工作正常即可,而更前面的映射模型都已经经过了测试。
总的想法就是手动找出当前版本 v 和版本 v+1 之间的映射模型,在这两者间迁移,接着继续递归,直到持久化存储与当前的数据模型兼容。
迁移的代码我依然贴上,至今,我任然没有搞明白两个数据源的路径应该怎么破,如果有人明白,请教教再下。
/**
* ************数据迁移************
*
* @param sourceStoreURL 源数据URL(.sqlite)
* @param type
* @param finalModel 目标数据模型(self.managedObjectModel)
* @param error
*
* @return 是否成功
*/
- (BOOL)progressivelyMigrateURL:(NSURL *)sourceStoreURL
ofType:(NSString *)type
toModel:(NSManagedObjectModel *)finalModel
error:(NSError **)error
{
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type
URL:sourceStoreURL
error:error];
//NSLog(@"sourceMetadata%@",sourceMetadata);
if (!sourceMetadata)
{
return NO;
}
//判断数据版本是否一致
if ([finalModel isConfiguration:nil
compatibleWithStoreMetadata:sourceMetadata]) {
if (NULL != error) {
*error = nil;
}
return YES;
}
NSManagedObjectModel *sourceModel = [self sourceModelForSourceMetadata:sourceMetadata];
//NSLog(@"sourceModel%@",sourceModel);
NSManagedObjectModel *destinationModel = nil;
NSMappingModel *mappingModel = nil;
NSString *modelName = nil;
//获取映像模型
if (![self getDestinationModel:&destinationModel
mappingModel:&mappingModel
modelName:&modelName
forSourceModel:sourceModel
error:error])
{
return NO;
}
//NSLog(@"sourceStoreURL:%@",sourceStoreURL);
// 我们现在有了一个映射模型,开始迁移
NSURL *destinationStoreURL = [self destinationStoreURLWithSourceStoreURL:sourceStoreURL
modelName:modelName];
//NSLog(@"destinationStoreURL:%@",destinationStoreURL);
NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel
destinationModel:destinationModel];
// NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
// [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
// [NSNumber numberWithBool:NO], NSInferMappingModelAutomaticallyOption, nil];
//NSLog(@"manager%@",manager);
if (![manager migrateStoreFromURL:sourceStoreURL
type:type
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStoreURL
destinationType:type
destinationOptions:nil
error:error]) {
return NO;
}
// 现在迁移成功了,把文件备份一下以防不测
if (![self backupSourceStoreAtURL:sourceStoreURL
movingDestinationStoreAtURL:destinationStoreURL
error:error]) {
return NO;
}
// 现在数据模型可能还不是“最新”版,所以接着递归
return [self progressivelyMigrateURL:sourceStoreURL
ofType:type
toModel:finalModel
error:error];
}
END