I'm having a very serious problem. The application is live, but unfortunately it's fails on iOS 5, and I need to post an update.
The thing is the ID column of few entities is in Integer 16, but I need to be changed to Integer 32.
It was clearly my mistake, the model was created very long time ago, and it was only being reused. To my surprise (now) on iOS 4, Integer 16 in Core Data could easily keep number as big as 500 000 (bug?), but it doesn't work like that now - it gives me invalid numbers.
Application is live, has it success and Core Data is also used to keep the users scores, achievements and so on, what I don't want to remove, forcing them to reinstall the application. What is the best approach to simply change about ten of properties in different entities from Integer 16 to Integer 32?
Of course I know the names and entities for those properties.
If I just change the Type column for those properties in the xcdatamodeld file it will work, for new user, but what about existing users, that already have sqlite file in their Documents folder. I believe I need to change the persistent store coordinator somehow.
And also what do you thing about the performance, there are about 10 properties that news to be changed from 16 to 32, but Core Data have in usual cases more than 100 000 objects inside.
Regards
Background
Previous version of app set attribute as 16 bit in Core Data.
This was too small to hold large values greater than approx 32768.
int 16 uses 1 bit to represent sign, so maximum value = 2^15 = 32768
In iOS 5, these values overflowed into negative numbers.
34318 became -31218
36745 became -28791
To repair these negative values, add 2^16 = 65536
Note this solution works only if the original value was less than 65536.
Add a new model
In file navigator, select MyApp.xcdatamodeld
Choose menu Editor/Add Model Version
Version name: proposes "MyApp 2" but you can change e.g. to MyAppVersion2
Based on model: MyApp
In new MyAppVersion2.xcdatamodel change attribute type from integer 16 to integer 64.
In file navigator, select directory MyApp.xcdatamodeld
Open right pane inspector, Versioned Core Data Model Current change from MyApp to MyAppVersion2. In left pane file navigator, green check mark moves from MyApp.xcdatamodel to MyAppVersion2.xcdatamodel.
In MyAppAppDelegate managedObjectModel do not change resource name from @"MyApp"
In Xcode select folder ModelClasses.
File/Add Core Data Mapping Model.
Choose source data model MyApp.xcdatamodel
Choose target data model MyAppVersion2.xcdatamodel
Save As MyAppToMyAppVersion2.xcmappingmodel
Add to target MyApp.
In MyAppAppDelegate persistentStoreCoordinator turn on CoreData manual migration
// 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_;
}
Add a migration policy
MyAppToMyAppVersion2MigrationPolicy
The following example converts one entity "Environment" with an integer attribute "FeedID" and a string attribute "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;
}
}
In file navigator select MyAppToMyAppVersion2.xcmappingmodel
In window, show right side utilities pane.
In window, select Entity Mappings EnvironmentToEnvironment
In right side Entity Mapping, choose Custom Policy enter MyAppToMyAppVersion2MigrationPolicy.
Save file.
References:
Zarra, Core Data Chapter 5 p 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.seattle-ipa.org/2011/09/11/coredata-and-integer-width-in-ios-5/
Privat, Pro Core Data for iOS Ch 8 p273