Core Data 将属性从 Integer 16 更改为 Integer 32

Posted

技术标签:

【中文标题】Core Data 将属性从 Integer 16 更改为 Integer 32【英文标题】:Core Data change property from Integer 16 to Integer 32 【发布时间】:2011-10-14 09:01:05 【问题描述】:

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

问题是少数实体的 ID 列是 Integer 16,但我需要更改为 Integer 32。

这显然是我的错误,模型是很久以前创建的,只是被重复使用。令我惊讶的是(现在)在 iOS 4 上,Core Data 中的 Integer 16 可以轻松地将数字保持在 500 000 (错误?),但它现在不像那样工作 - 它给了我无效的数字。

应用程序已上线,是否成功,Core Data 还用于保存用户的分数、成就等,我不想删除的内容,迫使他们重新安装应用程序。将不同实体中的大约十个属性从整数 16 更改为整数 32 的最佳方法是什么?

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

如果我只是更改 xcdatamodeld 文件中这些属性的 Type 列,对于新用户它将起作用,但对于现有用户,他们的 Documents 文件夹中已经有 sqlite 文件。我相信我需要以某种方式更改持久存储协调器。

还有你对性能有什么看法,大约有 10 个属性要从 16 更改为 32,但 Core Data 通常内部有超过 100 000 个对象。

问候

【问题讨论】:

【参考方案1】:

背景 以前版本的应用程序在 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

打开右窗格检查器,版本化核心数据模型当前从 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 以下示例使用整数属性“FeedID”和字符串属性“title”转换一个实体“Environment”。

- (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 在窗口中,显示右侧的实用程序窗格。 在窗口中,选择实体映射 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 Ch 8 p273 的 Pro 核心数据

【讨论】:

【参考方案2】:

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

注意:必须是自动迁移;使用映射模型进行手动迁移将不起作用。

【讨论】:

不幸的是,这不起作用。自动迁移不会修复现有的负整数值。 会发生什么?从 Int16 到 Int32 仍然允许负数。你能举个例子吗? 自动迁移确实修复了新的数据库字段。但是已经存储的值也需要迁​​移。快速解决方法是在从 db 读取这些值后修复它们: NSInteger myInt32 = databaseInt16 + 65536; 这没有意义。无论值有多大,SQLite 都会存储它们。 iOS 5.0 中引入的问题是,如果它们在 Int16 属性中大于 Int16,则会错误地读取它们。您所描述的问题可能发生的唯一方法是,如果您读错了(如上所述),然后又将其写回。在这种情况下,这几乎不是 Core Data 的错,CD 正在按预期工作。 这是不正确的。在 iOS 5 之前,Core Data 会正确存储该值,而不管您将上限设置为多少。 SQLite 将始终正确存储它。在 iOS 5 中,Core Data 发生了变化,因此任何比模型配置更大的东西都会导致问题。因此,如果您从 iOS 5 之前的商店迁移到 iOS 5,迁移将完美运行,这就是这个问题的意义所在。如果你已经在 iOS 5 下读过和写过数据库,那么你就被卡住了。如果您在 iOS 5 下立即迁移,那么这些值可以并且将会正确保留。【参考方案3】:

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

例如: 17 479 261 变为 18 851

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

我们从手机上下载了DB,查看了数据库,DB中的数字变了。

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

【讨论】:

以上是关于Core Data 将属性从 Integer 16 更改为 Integer 32的主要内容,如果未能解决你的问题,请参考以下文章

我应该使用 Core Data 中的 NSNumber (Integer 16, 32, 64) 来保留 NSUInteger

在 Core Data 中进行重量级迁移时尝试迁移 nil 属性值

Core Data Fetch 没有返回它应该返回的对象

如何在 Swift Int64 中使用 Core Data Integer 64?

Core Data 属性计数并在 TableView 中显示结果

从一个复制并转换为 Core Data 中的另一个属性