【问题标题】:CoreData versioning and blocking lightweight migrationCoreData 版本控制和阻塞轻量级迁移
【发布时间】:2012-03-06 17:35:03
【问题描述】:

我已经启用了我的核心数据模型的版本控制,并且一直在使用轻量级迁移。我的代码总是尝试进行轻量级迁移,然后如果由于模型不兼容而失败,它会退回到删除所有现有数据并从服务器重新获取。 所以轻量级迁移只是为了提高效率,而不是为了正确性。

我现在要做的是对我的模型进行更改,理论上轻量级迁移可以处理,但实际上我需要来自服务器的新数据。我想以某种方式标记模型,并且不能通过轻量级迁移进行升级。例如,如果字段名称未更改,但该字段的含义已更改,导致旧代码与新代码库不兼容。 (这只是一个例子。)

有没有人找到一种方法将两个模型标记为不兼容,以便轻量级迁移不会升级它们?

【问题讨论】:

    标签: iphone ios cocoa-touch core-data core-data-migration


    【解决方案1】:

    我以前也遇到过同样的问题。

    我有一个方法会尝试使用映射模型迁移数据,如果您要关闭轻量级迁移,您应该使用该方法。

    如果您不打算进行大量花哨的数据映射,xcode 将自动创建一个映射模型,该模型的工作方式与轻量级迁移完全相同。每次向 Core Data 添加新版本时,您所要做的就是创建一个新的“映射模型”文件。只需转到“文件 -> 新建 -> 新文件”,在核心数据下应该有一个映射模型模板。选择它并选择源和目标版本。

    我没有在 github 上公开我的代码,所以我将在这里发布迁移方法。

    - (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL ofType:(NSString*)type toModel:(NSManagedObjectModel*)finalModel
    {
        NSError *error = nil;
    
        // if store dosen't exist skip migration
        NSString *documentDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
        if(![NSBundle pathForResource:@"YongoPal" ofType:@"sqlite" inDirectory:documentDir])
        {
            migrationProgress = 1.0;
            [self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];
    
            // remove migration view
            [self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
            [self.migrationView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:YES];
    
            self.migrationView = nil;
            self.migrationProgressLabel = nil;
            self.migrationProgressView = nil;
            self.migrationSpinner = nil;
    
            return YES;
        }
    
        //START:progressivelyMigrateURLHappyCheck
        NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type URL:sourceStoreURL error:&error];
    
        if (!sourceMetadata)
        {
            return NO;
        }
    
        if ([finalModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata])
        {
            migrationProgress = 1.0;
            [self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];
    
            // remove migration view
            [self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
            [self.migrationView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:YES];
    
            self.migrationView = nil;
            self.migrationProgressLabel = nil;
            self.migrationProgressView = nil;
            self.migrationSpinner = nil;
    
            error = nil;
            return YES;
        }
        else
        {
            migrationProgress = 0.0;
            [self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:NO waitUntilDone:YES];
            [self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];        
        }
        //END:progressivelyMigrateURLHappyCheck
    
        //START:progressivelyMigrateURLFindModels
        //Find the source model
        NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMetadata];
        if(sourceModel == nil)
        {
            NSLog(@"%@", [NSString stringWithFormat:@"Failed to find source model\n%@", [sourceMetadata description]]);
            return NO;
        }
    
        //Find all of the mom and momd files in the Resources directory
        NSMutableArray *modelPaths = [NSMutableArray array];
        NSArray *momdArray = [[NSBundle mainBundle] pathsForResourcesOfType:@"momd" inDirectory:nil];
        for (NSString *momdPath in momdArray)
        {
            NSAutoreleasePool *pool = [NSAutoreleasePool new];
            NSString *resourceSubpath = [momdPath lastPathComponent];
            NSArray *array = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" inDirectory:resourceSubpath];
            [modelPaths addObjectsFromArray:array];
            [pool drain];
        }
    
        NSArray* otherModels = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" inDirectory:nil];
        [modelPaths addObjectsFromArray:otherModels];
    
        if (!modelPaths || ![modelPaths count])
        {
            //Throw an error if there are no models
            NSMutableDictionary *dict = [NSMutableDictionary dictionary];
            [dict setValue:@"No models found in bundle" forKey:NSLocalizedDescriptionKey];
    
            //Populate the error
            error = [NSError errorWithDomain:@"com.yongopal.coredata" code:500 userInfo:dict];
            if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
            {
                NSLog(@"error: %@", error);
            }
            return NO;
        }
        //END:progressivelyMigrateURLFindModels
    
        //See if we can find a matching destination model
        //START:progressivelyMigrateURLFindMap
        NSMappingModel *mappingModel = nil;
        NSManagedObjectModel *targetModel = nil;
        NSString *modelPath = nil;
    
        for(modelPath in modelPaths)
        {
            targetModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];
            mappingModel = [NSMappingModel mappingModelFromBundles:nil forSourceModel:sourceModel destinationModel:targetModel];
    
            //If we found a mapping model then proceed
            if(mappingModel)
            {
                break;
            }
            else
            {
                //Release the target model and keep looking
                [targetModel release];
                targetModel = nil;
            }
        }
    
        //We have tested every model, if nil here we failed
        if (!mappingModel)
        {
            NSMutableDictionary *dict = [NSMutableDictionary dictionary];
            [dict setValue:@"No mapping models found in bundle" forKey:NSLocalizedDescriptionKey];
            error = [NSError errorWithDomain:@"com.yongopal.coredata" code:500 userInfo:dict];
            if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
            {
                NSLog(@"error: %@", error);
            }
            return NO;
        }
        //END:progressivelyMigrateURLFindMap
    
        //We have a mapping model and a destination model.  Time to migrate
        //START:progressivelyMigrateURLMigrate
        NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:targetModel];
    
        // reg KVO for migration progress
        [manager addObserver:self forKeyPath:@"migrationProgress" options:NSKeyValueObservingOptionNew context:NULL];
    
        NSString *modelName = [[modelPath lastPathComponent] stringByDeletingPathExtension];
        NSString *storeExtension = [[sourceStoreURL path] pathExtension];
        NSString *storePath = [[sourceStoreURL path] stringByDeletingPathExtension];
    
        //Build a path to write the new store
        storePath = [NSString stringWithFormat:@"%@.%@.%@", storePath, modelName, storeExtension];
        NSURL *destinationStoreURL = [NSURL fileURLWithPath:storePath];
    
        if (![manager migrateStoreFromURL:sourceStoreURL type:type options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:type destinationOptions:nil error:&error])
        {
            if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
            {
                NSLog(@"error: %@", error);
            }
            [targetModel release];
            [manager removeObserver:self forKeyPath:@"migrationProgress"];
            [manager release];
            return NO;
        }
        [targetModel release];
        [manager removeObserver:self forKeyPath:@"migrationProgress"];
        [manager release];
        //END:progressivelyMigrateURLMigrate
    
        //Migration was successful, move the files around to preserve the source
        //START:progressivelyMigrateURLMoveAndRecurse
        NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
        guid = [guid stringByAppendingPathExtension:modelName];
        guid = [guid stringByAppendingPathExtension:storeExtension];
        NSString *appSupportPath = [storePath stringByDeletingLastPathComponent];
        NSString *backupPath = [appSupportPath stringByAppendingPathComponent:guid];
    
        NSFileManager *fileManager = [NSFileManager defaultManager];
        if (![fileManager moveItemAtPath:[sourceStoreURL path] toPath:backupPath error:&error])
        {
            if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
            {
                NSLog(@"error: %@", error);
            }
            //Failed to copy the file
            return NO;
        }
    
        //Move the destination to the source path
        if (![fileManager moveItemAtPath:storePath toPath:[sourceStoreURL path] error:&error])
        {
            if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
            {
                NSLog(@"error: %@", error);
            }
            //Try to back out the source move first, no point in checking it for errors
            [fileManager moveItemAtPath:backupPath toPath:[sourceStoreURL path] error:nil];
            return NO;
        }
    
        //We may not be at the "current" model yet, so recurse
        return [self progressivelyMigrateURL:sourceStoreURL ofType:type toModel:finalModel];
        //END:progressivelyMigrateURLMoveAndRecurse
    }
    

    这是我从一些我不记得标题的 Core Data 书籍中获得的方法的编辑版本。我希望我能把功劳归功于作者。 :S

    请注意,我这里有一些代码,你应该在你的实现中删除它们。这主要是我用来更新迁移进度视图的东西。

    你可以像这样使用这个方法:

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"YongoPal.sqlite"];
    
    // perform core data migrations if necessary
    if(![self progressivelyMigrateURL:storeURL ofType:NSSQLiteStoreType toModel:self.managedObjectModel])
    {
        // reset the persistent store on fail
        NSString *documentDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
        NSError *error = nil;
        [[NSFileManager defaultManager] removeItemAtPath:[NSBundle pathForResource:@"YongoPal" ofType:@"sqlite" inDirectory:documentDir] error:&error];
    }
    else
    {
        NSLog(@"migration succeeded!");
    }
    

    记得在使用之前删除轻量级迁移选项。

    【讨论】:

    • 我不知道这段代码如何解决我的问题。重申一下,我需要一种方式来说明两种模型不兼容。在 XCode 中创建映射模型文件后,我应该对它做些什么吗?
    • 创建映射模型后,尝试从 Xcode(4.x) 中查看它,它应该会拉出实体映射和属性映射列表。从属性映射列表中,您应该看到目标和值表达式。只需删除要从迁移中排除的属性的值表达式(应类似于 $source.{attribute_name}),使其为空。当您运行我的progresslyMigrateURL:ofType:toModel: 方法时,它将迁移除具有空值表达式的属性之外的所有内容。
    • 该方法来自 Marcus S. Zarra 的“Core Data: Apples API for persisting Data on Mac OSX”。我认为它现在称为“核心数据:iOS、OS X 和 iCloud 的数据存储和管理”..
    【解决方案2】:

    Apple 的Core Data Model Versioning and Data Migration Programming Guide 解决了如果“您有两个版本的模型,Core Data 通常将其视为等效的模型,而您希望被识别为不同的模型”,我认为这就是您要问的。

    答案是在新模型中的一个实体或属性上设置 versionHashModifier。

    在您的示例中,您将在含义已更改的字段上执行此操作。

    【讨论】:

      猜你喜欢
      • 2021-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-05
      • 1970-01-01
      • 1970-01-01
      • 2021-06-20
      相关资源
      最近更新 更多