美文网首页
CoreDara版本迁移、数据迁移

CoreDara版本迁移、数据迁移

作者: 纸简书生 | 来源:发表于2018-01-03 21:04 被阅读60次

    2018第一篇技术文章,之前写过一篇关于CoreData基础的文章Magical Record 全面解析。关于CoreData迁移相关的文章网上有一些,但是都不是特别全面,所以这里总结一下,一方面自己巩固,一方面希望能帮到需要的同学。

    CoreData迁移主要是两个方面,一个是数据库版本迁移,一个是数据迁移。

    Migration is required when the model doesn't match the store.

    首先推荐一个应用内调试的工具FLEX,可以直接查看数据库文件。

    CoreData数据库版本迁移

    数据库版本迁移比较简单。一般情况下是在新增了一张表之后,更新一下数据文件。

    选中xcdatamodel文件之后,点击editor。可以看到如下选项。


    这里包含了关于xcdatamodel大部分操作。选择Add Model Version。

    完成之后可以看到


    选中其中一个xcdatamodel文件,查看文件属性。这里包含了xcdatamodel各种属性,值得注意的是有个langua和coredatamodel,这两个后面会用到。


    选择当前版本为新建的版本既可以了。完成之后小绿勾就会显示在更改的版本上。

    CoreData数据迁移

    凡是会引起NSManagedObjectModel托管对象模型变化的,都最好进行数据迁移,防止用户升级应用之后就闪退。会引起NSManagedObjectModel托管对象模型变化的有以下几个操作,新增了一张表,新增了一张表里面的一个实体,新增一个实体的一个属性,把一个实体的某个属性迁移到另外一个实体的某个属性里面等等

    轻量级迁移

    能够通过自动推断的迁移叫做轻量级迁移。如果只是做了很小的改变,比如给实体新增了属性,CoreData能够根据自动推断做自动数据迁移。轻量级迁移与普通迁移基本相同,不同之处在于我们自己不用提供映射模型( mapping model)。

    如下场景可以使用轻量级迁移:

    • 简单的新增、删除属性
    • 重命名实体、属性
    • 属性的可选与不可选之间的变化
    • 可选变为不可选,并且定义了默认值。

    特别注意,如果改变属性类型,CoreData不能自动推断,也就不是轻量级迁移。

    轻量级迁移代码如下:

    NSError *error = nil;
    NSURL *storeURL = <#The URL of a persistent store#>;
    NSPersistentStoreCoordinator *psc = <#The coordinator#>;
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
        [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
        [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
     
    BOOL success = [psc addPersistentStoreWithType:<#Store type#>
                        configuration:<#Configuration or nil#> URL:storeURL
                        options:options error:&error];
    if (!success) {
        // Handle the error.
    }
    

    经过Demo测试可以总结出

    如果不用自动迁移则会出现如下现象:

    • 实体名字的修改会把之前的保存数据删除,原实体表删除。
    • 属性名称的修改之后保存数据全部删除。

    开启自动迁移则会:

    • 实体名字的修改会把之前的保存数据删除,原实体表删除。
    • 属性名称的修改之后数据能够成功迁移过来。

    顺便提一下,magicRecord只提供了轻量级迁移的方式。

    那么怎么判断CoreData是否可以进行自动迁移呢(其实所有的迁移都是都过NSMappingModel实现的,自动迁移也就是自动生成了NSMappingModel而已)。可以自定义,通过如下方法,分别传入源store,和目标store实现。

    - (BOOL)migrateStore:(NSURL *)storeURL toVersionTwoStore:(NSURL *)dstStoreURL error:(NSError **)outError {
     
        // Try to get an inferred mapping model.
        NSMappingModel *mappingModel =
            [NSMappingModel inferredMappingModelForSourceModel:[self sourceModel]
                            destinationModel:[self destinationModel] error:outError];
     
        // If Core Data cannot create an inferred mapping model, return NO.
        if (!mappingModel) {
            return NO;
        }
     
        // Create a migration manager to perform the migration.
        NSMigrationManager *manager = [[NSMigrationManager alloc]
            initWithSourceModel:[self sourceModel] destinationModel:[self destinationModel]];
     
        BOOL success = [manager migrateStoreFromURL:storeURL type:NSSQLiteStoreType
            options:nil withMappingModel:mappingModel toDestinationURL:dstStoreURL
            destinationType:NSSQLiteStoreType destinationOptions:nil error:outError];
     
        return success;
    }
    

    如果使用了MagicRecord,可以使用 [MagicalRecord setupAutoMigratingCoreDataStack]一行代码实现轻量级迁移。

    重量级迁移

    如果CoreData不能自动推断,就需要用稍微复杂的防护四去迁移数据。原理就是需要定义怎么去转换数据,所有的信息包含在映射模型中,映射模型是一系列迁移信息的集合。上面提到的轻量级迁移是通过自动生成NSMappingModel实现的,重量级迁移需要我们自己去创建NSMappingModel。Xcode提供了可视化的工具来创建映射模型。

    常见对象

    类比对象模型,CoreData提供了针对,模型,实体,属性的迁移工具(NSMappingModel, NSEntityMapping, 和 NSPropertyMapping).。

    • NSMappingModel:包含了NSEntityMapping,NSPropertyMapping的映射模型
    • NSEntityMapping:包含源实体,目标实体还有映射的类型(新增,移除,拷贝,或者转换)
    • NSPropertyMapping:包含在源实体和目标实体的名称,和一个表达式值。

    除此之外还提供了自定义的方式。

    可以在在Xcode的属性面板上直接使用自定义的表达式来做简单的迁移(复杂的迁移也是基于这些表达式)

    • 迁移从一个属性到另一个属性:比如amount属性重命名为totalCost。输入表达式$source.amount。
    • 转换值:比如由temperature华氏摄氏度到摄氏度,表达式($source.temperature - 32.0) / 1.8.

    一共有有6个预定义的key.

    NSMigrationManagerKey: $manager
    
    NSMigrationSourceObjectKey: $source
    
    NSMigrationDestinationObjectKey: $destination
    
    NSMigrationEntityMappingKey: $entityMapping
    
    NSMigrationPropertyMappingKey: $propertyMapping
    
    NSMigrationEntityPolicyKey: $entityPolicy
    

    通过Xcode创建映射模型

    这里以一个Student实体为例
    先插入数据,便于查看变化:

     [[NSManagedObjectContext MR_defaultContext] MR_saveWithBlock:^(NSManagedObjectContext * _Nonnull localContext) {
            for (int i = 0; i < 100; i++) {
                Student *sdt = [Student MR_createEntityInContext:localContext];
                sdt.name = [NSString stringWithFormat:@"sdt_%d",i];
                sdt.age = @(i);
            }
        } completion:^(BOOL contextDidSave, NSError * _Nullable error) {
            if (contextDidSave) {
                NSLog(@"Save Success");
            }
        }];
    

    创建新的xcdatamodel文件


    新建两个实体用于迁移Student中的age,name属性


    新建映射模型文件,并且选择源和目的xcdatamodel:


    创建完之后可以看到


    这里的$source.age和上面介绍得输入表达式一样。可以使用上面预定义的6个key

    需要特别注意一下下面的这个部分,迁移的规则都是从这里设置的

    设置好source和destination


    迁移的工作到这里就基本完成了,最后设置一下当前xcdatamodel文件的版。然后跑起来就可以在最新的数据库文件里面看到如下结果:

    确实多了两张表



    三张表的内容如下




    数据确实从Student表里面迁移到了StudentAge表和StudentName表。

    代码数据迁移

    通过上面可视化的操作已经可以达到迁移的目的了,通过代码进行迁移主要是在数据迁移过程中,如果你还想做一些什么其他事情,比如说你想清理一下垃圾数据,实时展示数据迁移的进度。

    • 直接上代码

    检测是否需要迁移:

    - (BOOL)isMigrationNecessaryForStore:(NSURL*)storeUrl
    {
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
        
        if (![[NSFileManager defaultManager] fileExistsAtPath:[self storeURL].path])
        {
            NSLog(@"SKIPPED MIGRATION: Source database missing.");
            return NO;
        }
        
        NSError *error = nil;
        NSDictionary *sourceMetadata =
        [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
                                                                   URL:storeUrl error:&error];
        NSManagedObjectModel *destinationModel = _coordinator.managedObjectModel;
        
        if ([destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata])
        {
            NSLog(@"SKIPPED MIGRATION: Source is already compatible");
            return NO;
        }
        
        return YES;
    }
    
    • 如何进行迁移:
    - (BOOL)migrateStore:(NSURL*)sourceStore {
        
        NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd));
        BOOL success = NO;
        NSError *error = nil;
        
        // STEP 1 - 收集 Source源实体, Destination目标实体 和 Mapping Model文件
        NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator
                                        metadataForPersistentStoreOfType:NSSQLiteStoreType
                                        URL:sourceStore
                                        error:&error];
        
        NSManagedObjectModel *sourceModel =
        [NSManagedObjectModel mergedModelFromBundles:nil
                                    forStoreMetadata:sourceMetadata];
        
        NSManagedObjectModel *destinModel = _model;
        
        NSMappingModel *mappingModel =
        [NSMappingModel mappingModelFromBundles:nil
                                 forSourceModel:sourceModel
                               destinationModel:destinModel];
        
        // STEP 2 - 开始执行 migration合并, 前提是 mapping model 不是空,或者存在
        if (mappingModel) {
            NSError *error = nil;
            NSMigrationManager *migrationManager =
            [[NSMigrationManager alloc] initWithSourceModel:sourceModel
                                           destinationModel:destinModel];
            [migrationManager addObserver:self
                               forKeyPath:@"migrationProgress"
                                  options:NSKeyValueObservingOptionNew
                                  context:NULL];
    NSURL *destinStore =
            [[self applicationStoresDirectory]
             URLByAppendingPathComponent:@"Temp.sqlite"];
            
            success =
            [migrationManager migrateStoreFromURL:sourceStore
                                             type:NSSQLiteStoreType options:nil
                                 withMappingModel:mappingModel
                                 toDestinationURL:destinStore
                                  destinationType:NSSQLiteStoreType
                               destinationOptions:nil
                                            error:&error];
            if (success)
            {
                // STEP 3 - 用新的migrated store替换老的store
                if ([self replaceStore:sourceStore withStore:destinStore])
                {
                    NSLog(@"SUCCESSFULLY MIGRATED %@ to the Current Model",
                              sourceStore.path);
                    [migrationManager removeObserver:self
                                          forKeyPath:@"migrationProgress"];
                }
            }
            else
            {
                NSLog(@"FAILED MIGRATION: %@",error);
            }
        }
        else
        {
            NSLog(@"FAILED MIGRATION: Mapping Model is null");
        }
        
        return YES; // migration已经完成
    }
    
    
    • 迁移进度有变化,会通过观察者,observeValueForKeyPath来告诉用户进度,这里可以监听该进度,如果没有完成,可以来禁止用户执行某些操作。
    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary *)change
                           context:(void *)context {
        
        if ([keyPath isEqualToString:@"migrationProgress"]) {
            
            dispatch_async(dispatch_get_main_queue(), ^{
                
                float progress =
                [[change objectForKey:NSKeyValueChangeNewKey] floatValue];
              
                int percentage = progress * 100;
                NSString *string =
                [NSString stringWithFormat:@"Migration Progress: %i%%",
                 percentage];
                NSLog(@"%@",string);
    
            });
        }
    }
    

    代码迁移这块,读者可以自己试一试。

    扩展阅读

    Wha Is Core Data?
    Next Core Data Model Versioning and Data Migration
    iOS Core Data 数据迁移 指南

    相关文章

      网友评论

          本文标题:CoreDara版本迁移、数据迁移

          本文链接:https://www.haomeiwen.com/subject/ctmknxtx.html