核心数据更改属性从Integer 16到Integer 32

时间:2011-10-14 09:01:05

标签: xcode core-data core-data-migration

我遇到了一个非常严重的问题。该应用程序是实时的,但不幸的是它在iOS 5上失败了,我需要发布更新。

少数实体的ID列在Integer 16中,但我需要更改为Integer 32。

显然是我的错误,这个模型是很久以前创建的,它只是被重用了。令我惊讶的是(现在)在iOS 4上,核心数据中的整数16可以很容易地保持数字大到500 000(错误?),但它现在不能正常工作 - 它给了我无效的数字。

应用程序是实时的,它是否成功,Core Data还用于保持用户的分数,成就等等,我不想删除,迫使他们重新安装应用程序。简单地将不同实体中的十个属性从Integer 16更改为Integer 32的最佳方法是什么?

当然,我知道这些属性的名称和实体。

如果我只是更改xcdatamodeld文件中这些属性的Type列,它对新用户有效,但现有用户的文档文件夹中已经有sqlite文件。我相信我需要以某种方式更改持久性商店协调员。

而且你对性能有什么看法,大约有10个属性可以将消息从16改为32,但Core Data在通常情况下有超过10万个对象。

此致

3 个答案:

答案 0 :(得分:12)

<强>背景
以前版本的app set属性为Core Data中的16位。 这太小了,不能容纳大于32768的大值 int 16使用1位来表示符号,因此最大值= 2 ^ 15 = 32768
在iOS 5中,这些值溢出为负数。

34318变为-31218
36745成为-28791

要修复这些负值,请添加2 ^ 16 = 65536
请注意,此解决方案仅在原始值小于65536时有效。

添加新模型
在文件导航器中,选择MyApp.xcdatamodeld
选择菜单编辑器/添加模型版本
版本名称:建议“MyApp 2”,但您可以更改,例如到MyAppVersion2
基于型号:MyApp

在新的MyAppVersion2.xcdatamodel中,将属性类型从整数16更改为整数64。

在文件导航器中,选择目录MyApp.xcdatamodeld

打开右窗格检查器,Versioned Core Data Model当前从MyApp更改为MyAppVersion2。 在左窗格文件导航器中,绿色复选标记从MyApp.xcdatamodel移动到MyAppVersion2.xcdatamodel。

在MyAppAppDelegate中,managedObjectModel不会从@“MyApp”更改资源名称

在Xcode中选择文件夹ModelClasses 文件/添加核心数据映射模型。

选择源数据模型MyApp.xcdatamodel
选择目标数据模型MyAppVersion2.xcdatamodel
另存为MyAppToMyAppVersion2.xcmappingmodel

添加到目标MyApp。

在MyAppAppDelegate中,persistentStoreCoordinator启用CoreData手动迁移

// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created
// and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

    if (persistentStoreCoordinator_ != nil) {
        return persistentStoreCoordinator_;
    }

    NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] 
                                               stringByAppendingPathComponent: @"MyApp.sqlite"]];

    NSError *error = nil;
    persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] 
                                   initWithManagedObjectModel:[self managedObjectModel]];

    // Set Core Data migration options
    // For automatic lightweight migration set NSInferMappingModelAutomaticallyOption to YES
    // For manual migration using a mapping model set NSInferMappingModelAutomaticallyOption to NO
    NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], 
                                       NSMigratePersistentStoresAutomaticallyOption, 
                                       [NSNumber numberWithBool:NO], 
                                       NSInferMappingModelAutomaticallyOption, 
                                       nil];

    if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType 
                                                   configuration:nil 
                                                             URL:storeURL 
                                                         options:optionsDictionary 
                                                           error:&error])
    {
        // handle the error
        NSString *message = [[NSString alloc]
                             initWithFormat:@"%@, %@", error, [error userInfo]];

        UIAlertViewAutoDismiss *alertView = [[UIAlertViewAutoDismiss alloc]     
                                             initWithTitle:NSLocalizedString(@"Sorry, Persistent Store Error.  Please Quit.", @"")
                                             message:message
                                             delegate: nil
                                             cancelButtonTitle:NSLocalizedString(@"OK", @"") 
                                             otherButtonTitles:nil]; 
        [message release];
        [alertView show];
        [alertView release];
    }    

    return persistentStoreCoordinator_;
}

添加迁移政策
MyAppToMyAppVersion2MigrationPolicy
以下示例将一个实体“Environment”转换为整数属性“FeedID”和字符串属性“title”。

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)aSource
                                  entityMapping:(NSEntityMapping *)mapping
                                        manager:(NSMigrationManager *)migrationManager
                                          error:(NSError **)error {
    NSEntityDescription *aSourceEntityDescription = [aSource entity];
    NSString *aSourceName = [aSourceEntityDescription valueForKey:@"name"];

    NSManagedObjectContext *destinationMOC = [migrationManager destinationContext];
    NSManagedObject *destEnvironment;
    NSString *destEntityName = [mapping destinationEntityName];

    if ([aSourceName isEqualToString:kEnvironment])
    {
        destEnvironment = [NSEntityDescription
                           insertNewObjectForEntityForName:destEntityName
                           inManagedObjectContext:destinationMOC];

        // attribute feedID
        NSNumber *sourceFeedID = [aSource valueForKey:kFeedID];
        if (!sourceFeedID)
        {
            // Defensive programming.
            // In the source model version, feedID was required to have a value
            // so excecution should never get here.
            [destEnvironment setValue:[NSNumber numberWithInteger:0] forKey:kFeedID];
        } 
        else
        {
            NSInteger sourceFeedIDInteger = [sourceFeedID intValue];        
            if (sourceFeedIDInteger < 0)
            {            
                // To correct previous negative feedIDs, add 2^16 = 65536            
                NSInteger kInt16RolloverOffset = 65536;            
                NSInteger destFeedIDInteger = (sourceFeedIDInteger + kInt16RolloverOffset);
                NSNumber *destFeedID = [NSNumber numberWithInteger:destFeedIDInteger]; 
                [destEnvironment setValue:destFeedID forKey:kFeedID];

            } else
            {
                // attribute feedID previous value is not negative so use it as is
                [destEnvironment setValue:sourceFeedID forKey:kFeedID];
            }
        }

        // attribute title (don't change this attribute)
        NSString *sourceTitle = [aSource valueForKey:kTitle];
        if (!sourceTitle)
        {
            // no previous value, set blank
            [destEnvironment setValue:@"" forKey:kTitle];
        } else
        {
            [destEnvironment setValue:sourceTitle forKey:kTitle];
        }

        [migrationManager associateSourceInstance:aSource
                          withDestinationInstance:destEnvironment
                                 forEntityMapping:mapping];

        return YES;
    } else
    {
        // don't remap any other entities
        return NO;
    }
}

在文件导航器中,选择MyAppToMyAppVersion2.xcmappingmodel
在窗口中,显示右侧实用程序窗格 在窗口中,选择Entity Mappings EnvironmentToEnvironment
在右侧实体映射中,选择自定义策略,输入MyAppToMyAppVersion2MigrationPolicy 保存文件。

<强>参考文献:

Zarra,核心数据第5章第87页http://pragprog.com/book/mzcd/core-data

http://www.informit.com/articles/article.aspx?p=1178181&seqNum=7

http://www.timisted.net/blog/archive/core-data-migration/

http://www.cocoabuilder.com/archive/cocoa/286529-core-data-versioning-non-trivial-value-expressions.html

http://www.seattle-ipa.org/2011/09/11/coredata-and-integer-width-in-ios-5/

Privat,适用于iOS的Pro Core数据第8章第273页

答案 1 :(得分:9)

启用NSMigratePersistentStoresAutomaticallyOption' and 'NSInferMappingModelAutomaticallyOption中的NSPersistentStore,然后使用更改创建模型的第二个版本。仅进行整数更改以保持迁移简单。这将允许安装升级的用户从损坏的模型迁移到更正的模型。

注意:必须是自动迁移;使用映射模型进行手动迁移将无法正常工作。

答案 2 :(得分:0)

只是想通过一个小小的补充确认Marcus S. Zarra的回答。它在某种程度上对我们有益。我们在模型中犯了完全相同的错误。但它有一个问题。在自动迁移期间,超过似乎为2 ^ 24的值将转换为16位值,但保存为32位但值不正确。

例如: 17 479 261变为18 851

(17 479 261 mod(2 ^ 16)) - (2 ^ 16)= -18 851

我们从手机下载了数据库并查看了数据库,并在数据库中更改了数字。

我们还没有解决这个问题。