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 属性值
如何在 Swift Int64 中使用 Core Data Integer 64?