【优化篇】coreData数据迁移

前面有有写过一篇关于coredata简单使用的教程【进阶篇】iOS coreData简单使用教程

说起coredata的数据迁移,解决不当的后果是 <code>闪退</code>,这种问题出现的原因是在版本迭代的过程中,对数据模型进行了修改,而老版本的用户在进行App升级的时候,数据结构无法对应,导致App在一加载的时候就会直接闪退,无法进入App,这对用户体验来说是非常致命的。

Method one

下面看一个<code>crash log</code>

<pre><code class= 'objectivec hljs'>
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 = <ef8527a5 4ab87f7c 5dd07c76 d606f4f8 7c930899 f625282a 570021e0 72b42f92>;
HSDInvestInfo = <8b1f6048 2bfd9345 bf39aa1b 73867b56 3bcd5880 be28681f e3ffcb83 c4cc09ef>;
HSDLoanDetailInfo = <67a48388 0aa3cee7 31113c81 9599a537 cee77239 7a12ba48 ff18c599 3ca1c6e5>;
HSDLoanListInfo = <dedfb6cc 7afc233e f543885e 578024f7 0f3743b7 6efe021c 423f7469 2d7e5de2>;
HSDNewsInfo = <fb9f10ca 80035548 c8518f47 6ccf1079 40ab7ddb 609974c8 ffea1b84 40a77ac9>;
HSDReturnAmtInfo = <2c0ede3a 48523624 42a1b64e 85f75755 a7594f01 8c10c62d 0fb71f99 5fb94535>;
HSDTenderInfo = <af245a1f eea1cfea 9b83f565 47c7c03a 4ef4004b 57c32836 da39ccf7 aee7bb66>;
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 = <ef8527a5 4ab87f7c 5dd07c76 d606f4f8 7c930899 f625282a 570021e0 72b42f92>;
HSDInvestInfo = <8b1f6048 2bfd9345 bf39aa1b 73867b56 3bcd5880 be28681f e3ffcb83 c4cc09ef>;
HSDLoanDetailInfo = <67a48388 0aa3cee7 31113c81 9599a537 cee77239 7a12ba48 ff18c599 3ca1c6e5>;
HSDLoanListInfo = <dedfb6cc 7afc233e f543885e 578024f7 0f3743b7 6efe021c 423f7469 2d7e5de2>;
HSDNewsInfo = <fb9f10ca 80035548 c8518f47 6ccf1079 40ab7ddb 609974c8 ffea1b84 40a77ac9>;
HSDReturnAmtInfo = <2c0ede3a 48523624 42a1b64e 85f75755 a7594f01 8c10c62d 0fb71f99 5fb94535>;
HSDTenderInfo = <af245a1f eea1cfea 9b83f565 47c7c03a 4ef4004b 57c32836 da39ccf7 aee7bb66>;
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";
}
</code></pre>
以上在在程序启动的时候打印的崩溃信息,可以看到说是没有找到对应的实体文件,在iOS9.0以下,如果对数据模型做了改动,仅仅在<code>persistentStoreCoordinator</code>中添加以下代码,是没有用的
<pre><code class = 'objectivec hljs'>
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
</code></pre>
经多次验证,这中迁移方式,在iOS9.0以下仍然会存在闪退,所有需要更加高级的迁移方式,并且有些地方需要注意:

1、不要动原来的<code>Model.xcdatamodeld</code>文件,不能直接在这个文件中添加行的表,或者添加字段。
2、模型版本控制,保留原来的模型版本,在原来模型版本的基础上创建一个新的映射模型来做改动,选中<code>Model.xcdatamodeld</code>文件,Editor->Add Model Version,选择一个基础模型,命名为<code>Model_to_Model2</code>,意味着这是一个以Model为基础改动的模型。


image

3、创建好了之后,我们会得到<code>Model_to_Model2.xcmappingmodel</code>文件,点击它查看,其实它会和Model里面的内容一摸一样,接下来,我们将需要新增的表,或者字段,添加在这个映射模型中就行了,这样就完成了一次模型映射的数据迁移了。


Method tow

这个方法粗暴简单,不到特殊的情况(找不出解决办法的时候),推荐使用这个,数据迁移的报错都是因为数据模型文件不符的原因,我们在程序第一次启动的时候,将原来的旧的文件删除,在重新创建新的就行了。
<pre><code class = 'objectivec hljs'>

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]);

}
</code></pre>

.sqlite的文件在<code>Document</code>文件目录下,这个方法只需要传入文件的名字,就可以删除掉了。
注意:必须在程序启动时,在未调用coraData的上下文对象前调用这个方法来删除原来的文件,因为coredata的持久化对象使用的是懒加载方法,如果没有调用,就不会闪退,可以尽量将这个方法放在<code>didFinishLaunchingWithOptions</code>方法的靠前位置,删除了原来的数据库文件,原来版本所有的缓存数据将<code>不复存在</code>(怪我咯~)


Method three

<code>渐进式迁移</code>

关于这种方法,可以先来看一下当初我做数据迁移时候写的感想:

这次是数据迁移并没有成功,最后仍然是通过将原来的数据库文件删除的方式得到的解决。归纳其中的原因很可能是我动了原来的数据模型,然后对Coredata数据迁移的也并不是很了解,目前已经不知道在其中的某个改动过程出现了什么问题在使用coredata的过程中,非常需要注意的事情是,一旦提交了一个版本后,绝对不能再对原来的数据模型进行复制的标结构操作(如添加表,多表迁移改动数据)这样的操作必须要先添加一个Model version,然后再去在这个modek version中进行复制的表结构操作,如果是轻微的改动,使用轻量级的数据迁移即可,如果是复杂的改动,苹果官网的文档上推荐使用手动指引数据迁移,包括非常重要的多版本控制。
地址:苹果官网对数据迁移的文档描述

这个方法是一个渐进式迁移 (Progressive Migrations)
与其为每个之前的数据模型到最新的模型间都建立映射模型,还不如在每两个连续的数据模型之间创建映射模型。以前面的例子来说,版本 1 和版本 2 之间需要一个映射模型,版本 2 和版本 3 之间需要一个映射模型。这样就可以从版本 1 迁移到版本 2 再迁移到版本 3。显然,使用这种迁移的方式时,若用户在较老的版本上迁移过程就会比较慢,但它能节省开发时间并保证健壮性,因为你只需要确保从之前一个模型到新模型的迁移工作正常即可,而更前面的映射模型都已经经过了测试。

总的想法就是手动找出当前版本 v 和版本 v+1 之间的映射模型,在这两者间迁移,接着继续递归,直到持久化存储与当前的数据模型兼容。

迁移的代码我依然贴上,至今,我任然没有搞明白两个数据源的路径应该怎么破,如果有人明白,请教教再下。
<pre><code class='objectivec hljs'>
/**

  • ************数据迁移************
  • @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];
    }

</code></pre>

END

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

推荐阅读更多精彩内容