iPhone Core Data "Automatic Lightweight Migration" - ios

I am attempting to update an app that implements a core data store. I am adding an attribute to one of the entities.
I added the following code to my delegate class:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"Shoppee.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
NSLog(#"Error: %#",error);
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}
This was from the following URL:
Doc
I get the following error when executing the code:
2009-12-01 20:04:22.877
Shoppee[25633:207] Error: Error
Domain=NSCocoaErrorDomain Code=134130
UserInfo=0x1624d20 "Operation could not be completed. (Cocoa error
134130.)" 2009-12-01 20:04:22.879 Shoppee[25633:207] Unresolved error
Error Domain=NSCocoaErrorDomain Code=134130 UserInfo=0x1624d20
"Operation could not be completed. (Cocoa error 134130.)", {
URL = file://localhost/Users/Eric/Library/Application%20Support/iPhone%20Simulator/User/Applications/A8A8FB73-9AB9-4EB7-8F83-82F5B4467AF1/Documents/MyApp.sqlite;
metadata = {
NSPersistenceFrameworkVersion = 241;
NSStoreModelVersionHashes = {
Item = <869d4b20 088e5c44 5c345006 87d245cd 67ab9bc4 14cadf45
180251e9 f741a98f>;
Store = <47c250f4 895e6fd1 5033ab42 22d2d493 7819ba75 3c0acffc
2dc54515 8deeed7a>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
);
NSStoreType = SQLite;
NSStoreUUID = "8DC65301-3BC5-42BE-80B8-E44577B8F8E1";
};
reason = "Can't find model for source store"; }
It looks like I somehow need to include the original data model but I am not sure how to do that. Any suggestions?

To recap/Full guide:
Before making any change, create a new model version.
In Xcode 4: Select your .xcdatamodel -> Editor -> Add Model Version.
In Xcode 3: Design -> Data Model -> Add Model Version.
You will see that a new .xcdatamodel is created in your .xcdatamodeld folder (which is also created if you have none).
Save.
Select your new .xcdatamodel and make the change you wish to employ in accordance with the Lightweight Migration documentation.
Save.
Set the current/active schema to the newly created schema.
With the .xcdatamodeld folder selected:
In Xcode 4: Utilities sidebar -> File Inspector -> Versioned Core Data Model -> Select the new schema.
In Xcode 3: Design > Data Model > Set Current Version.
The green tick on the .xcdatamodel icon will move to the new schema.
Save.
Implement the necessary code to perform migration at runtime.
Where your NSPersistentStoreCoordinator is created (usually AppDelegate class), for the options parameter, replace nil with the following code:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]
Run your app. If there's no crash, you've probably successfully migrated :)
When you have successfully migrated, the migration code (step 7) can be removed. (It is up to the developer to determine when the users of a published app can be deemed to have migrated.)
IMPORTANT: Do not delete old model versions/schemas. Core Data needs the old version to migrate to the new version.

I figured it out.
Design > Data Model > Add Model Version

For Googlers again, this is what you need to do (assuming you have already set up Lightweight Migration):
Before making changes, Do Design -> Data Model -> Add Model Version (you will see that a new .xcdatamodel is created in your .xcdatamodeld folder)
Save
Make your change
Save
Run App
Step #1 is crucial for making this work. I ran into this problem because I had followed these steps to add a new field. That worked. I added a second new field, but forgot to "Add Model Version", and things blew up.

Also for googlers.. Simple rule, never delete/edit any old numbered version. When you Add Model Version the number suffix will increase as 2..3..4 meaning 2 is the oldest 3 next etc.. but the Current one to edit is the unnumbered version.
Do not delete old model versions as users with previous db using an old model version will not be able to migrate to your latest db model with out comparing old and latest schemas.

Just a note for those that come across this Googling, it seems even with auto(magic) migration you still need to create a version of your original store, and a new one, and set the new one as the current version.

So far I only see how to avoid the error message.
But how do we fix it - in case we messed things up already??
The following solution fixed the problem but you will loose the data in the DB:
Delete / rename the sqlite file of the deployed / installed application.
The files name an location are given directly after the error message. e.g.:
reason=Can't find model for source store}, {
URL = "file://localhost/Users/yourName/Library/Application%20Support/iPhone%20Simulator/4.3/Applications/62F342D4-F007-4F6F-96D2-68F902D3719A/Documents/Locations.sqlite";

Something to keep in mind when doing a lightweight migration -
If you plan to rename/modify attributes, remember to set the "Renaming ID" value in either the new or the old model. To use Apple's own example, in XCode 4.3, select paintColor in the new model > switch to the Data Model Inspector > Set the "Renaming ID" field to Color in the "Versioning" section. For me, failure to do this step led to a run time error. This same error is also covered here. As a new user, I'm not allowed to post images, so here's an imgur link (not spam, really).
(Cocoa error 134140.)" UserInfo=0x622b350 {reason=Can't find or automatically infer mapping model for migration

You can also get this error when making a change to the data model and running on an installed app that has a different version of the sqlite file. In this case just delete the installed app and re-run it.

Just in case someone runs into this scenario and none of the above works... I was deleting my app from the simulator, cleaning, etc, but nothing would work. I had to go to the simulator directory and manually rm the .sqlite file to get the app working again. No clue...

Related

Core Data - can't achieve a simple LightWeight Migration

I am unable to achieve a simple lightweight migration by simply adding 1 Entity to the datamodel.
I have read and followed all the guides/documentation/posts/answers, I can't seem to find my mistake/error.
I do have created a new datamodel from the already existing one.
I do have set the new datamodel as current datamodel.
I do have only added 1 Entity to the new datamodel (+ link to the parent Entity).
I do have passed the dictionary options NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption in the method addPersistentStoreWithType.
I even tried to log everything, thank to the method given from this post: core data migration
/*! The method checks the Core Data file version is compatible with the App's model version
and then pushes the main menu view onto the navigation stack. If not compatible it displays a
message to the user.
#param file The file URL for the Core Data Store. With UIManagedDocument you have to get the
actual store file URL, you can't just use the UIManagedDocument file URL.
*/
-(void) checkCoreDataFileVersion:(NSURL*)file
{
if ([self checkVersion:file]) {
// file version is compatible so continue (add code to push the menu view)
} else {
// file version is NOT compatible
_fileOpenErrorAlert = [[UIAlertView alloc] initWithTitle:#"Unable to open Document" message:#"Please check that you have the correct application version installed" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[_fileOpenErrorAlert show];
}
return;
}
/*! Checks the Core Data files models version against the apps model version to see if they
are compatible. This will return YES if a lightweight migration can be performed and NO if NOT.
#param fileURL The file URL for the Core Data Store. With UIManagedDocument you have to get the
actual store file URL, you can't just use the UIManagedDocument file URL.
#return Returns YES if they are compatible and NO if not.
*/
- (bool)checkVersion:(NSURL*)fileURL {
NSManagedObjectModel *model = [self managedObjectModel];
NSLog(#" app model entity version hashes are %#", [model entityVersionHashesByName]);
NSError *error;
NSDictionary *metaData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:fileURL error:&error];
if (!metaData) {
NSLog(#"problem getting metaData");
NSLog(#" - error is %#, %#", error, error.userInfo);
return NO;
}
bool result = [model isConfiguration:nil compatibleWithStoreMetadata:metaData];
if (!result) {
NSLog(#" file is not compatible!");
NSLog(#" metadata is %#", metaData);
}
return result;
}
When I make a diff of the metadata from all the Entities, I only match difference for 1 Entity (the newly created). So why it can't make a migration ? I just added 1 Entity.
EDIT :
I don't have Crashes, the App is working fine.
There is something I don't understand. When I download our lastest App from the AppStore, launch it and when I build from xCode my lastest developement App (with the new datamodel) over the one from the AppStore, the migration doesn't occur.
BUT when I use GIT, when I put the HEAD to the lastest release TAG, build, launch the App. Then put back the HEAD to my lastest development feature (with the new datamodel etc..), build and run, the migration is done and everything is working.
So which scenario should I trust ?
Yes, You should trust the 2nd senario to test coredata migration by applying it to the last released code.
The first senario is no more valid since Apple for some security reasons nomore give the ability to update an itune-downloaded app using xcode directly.
There was a way to test the upgrade on itune-version but not directly from xcode.
Technical Note TN2285
Testing iOS App Updates
Install an ad hoc distribution of an archived build of the update
using iTunes on a device that already has the old version of the app
installed.
Installing Your App on Test Devices Using iTunes

Core Data - Lightweight migration doesn't work

I am new to Core Data, and I am currently maintaining an application using Core Data.
In the need of publishing a new release of the application soon, I have to add en Entity to the data model.
I have followed this tutorial Lightweight tutorial which was very useful but also I have read all the Apple documentation but also this amazing article Core Data Migration in order to understand globaly how it works.
Although I had to add only one entity to the data model, I heard that a Lightweight migration was OK in this situation.
It's only 1 new Entity (without attributes) that I have to link to the already existing parent Entity.
What I have done so far :
The application is currently on the version 3 of the datamodel
I have created a new data model (version 4) from the version 3
I have chosen data model version 4 as current data model
I have created my new Entity (whithout attribute), and linked it to the parent Entity.
I have created the generated class object
Then I modified my UI
Build and run, it works, cool. BUT when I download the current version from the AppStore, and when I install the new recently made Archive/IPA from TestFlight, (install over the old one -> migration scenario) the Application run without my new Entity/Datamodel.
From the Apple documentation, it is very clear that adding Entity is supported by Core Dara for Lightweight Migration.
I know this is not an easy process, but I feel like I have followed everything perfectly.
How can I test the migration without each time archive, publish on TestFlight etc...
If you need any additional informations in order to clearly understand my issue and/or write a more elaborated answer, feel free to ask in the comment and I will edit my question.
Thank you in advance.
EDIT :
Here are the code about the NSPersistentStoreCoordinator from the AppDelegate.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it.
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Create the coordinator and store
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [self persistentStoreURL];
NSError *error = nil;
NSString *failureReason = #"There was an error creating or loading the application's saved data.";
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES} error:&error]) {
// Report any error we got.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSLocalizedDescriptionKey] = #"Failed to initialize the application's saved data";
dict[NSLocalizedFailureReasonErrorKey] = failureReason;
dict[NSUnderlyingErrorKey] = error;
error = [NSError errorWithDomain:#"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
DDLogError(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
I don't know how can I effectively know by tests or logs that CoreData use the new data model (version 4) and has performed the migration successfully.
I know it works when I build from xCode but it's not the same situation than an update from the AppStore.
To set up an app for a lightweight migration, you need to insert the following lines into your CoreData framework where the Persistent Store is declared. These settings enable the options that support lightweight migrations (these are from a Swift 3.0 app so they may vary a bit if you're in 2.3):
NSMigratePersistentStoresAutomaticallyOption as NSObject: true
NSInferMappingModelAutomaticallyOption as NSObject: true
Once these lines are in place, CoreData will perform lightweight migrations correctly whenever they're required, including adding new entities, attributes, and relationships so you should be OK as long as you don't do anything that requires more action on your part - like changing the name of an entity or property.

Remove Core Data from iCloud fails

I'm trying to remove Core Data from iCloud using [NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:options:error:].
But I get strange output:
__93+[NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:options:error:]_block_invoke(1982):
CoreData: Ubiquity: Unable to move content directory to new location:
file:///private/var/mobile/Library/Mobile%20Documents/<UBIQUITY_ID>/
New: file:///private/var/mobile/Library/Mobile%20Documents/OldUbiquitousContent-mobile~C9439AD0-1E87-4977-9C68-0674F5E2E93B
Error Domain=NSCocoaErrorDomain Code=513 "The operation couldn’t be completed.
(Cocoa error 513.)" UserInfo=0x181ab790 {NSSourceFilePathErrorKey=/private/var/mobile/Library/Mobile Documents/<UBIQUITY_ID>,
NSUserStringVariant=(
Move
), NSFilePath=/private/var/mobile/Library/Mobile Documents/<UBIQUITY_ID>,
NSDestinationFilePath=/private/var/mobile/Library/Mobile Documents/OldUbiquitousContent-mobile~C9439AD0-1E87-4977-9C68-0674F5E2E93B,
NSUnderlyingError=0x181aab50 "The operation couldn’t be completed. Operation not permitted"}
What does it mean?
How to avoid it? I'm working on iCloud disable/enable feature. Details HERE
UPDATE:
NSDictionary *iCloudOptions =
[NSDictionary dictionaryWithObjectsAndKeys:kICloudContentNameKey, NSPersistentStoreUbiquitousContentNameKey,
iCloudURL, NSPersistentStoreUbiquitousContentURLKey, nil];
// self.lastICloudStoreURL stores NSPersistentStore.URL after stack setup
BOOL result = [NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:self.lastICloudStoreURL
options:iCloudOptions
error:&error];
Normally (before iOS7), you take the ubiquitousContentURL value from [fileManager URLForUbiquityContainerIdentifier:nil];
and pass it as an option called NSPersistentStore UbiquitousContentURLKey, and this is how iCloud know where to keep all of your data in the iCloud account.
In iOS 7 and Mac OS X we don't need to pass a value for that at all, and Apple call URLForUbiquitous ContainerIdentifier automatically under the hood for you.
So the solution looks like this:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:kICloudContentNameKey, NSPersistentStoreUbiquitousContentNameKey, nil];
NSURL *storeURL = [NSPersistentStore MR_urlForStoreName:[MagicalRecord defaultStoreName]];
BOOL result = [NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:storeURL options:options error:&error];
I suggest you to check WWDC 2013 session 207 to get this things clearly.
NSCocoaErrorDomain error 513 is defined in FoundationErrors.h as NSFileWriteNoPermissionError. You don't have the required permissions to write to the location.
This can happen if a managed object context is still using objects that are backed by this store. The context is actively using the file that is being moved, which results in a NSFileCoordinator conflict. Two things are trying to access the file with write permission at the same time.
The removeUbiquitousContentAndPersistentStoreAtURL:options:error: method deletes all of the user's local and cloud data—this is probably not what you want. Instead, migrate your store to a new location on disk and use the NSPersistentStoreRemoveUbiquitousMetadataOption option with the migratePersistentStore:toURL:options:withType:error: method.
See Disabling iCloud Persistence at Removing an iCloud-enabled Persistent Store in the iCloud Programming Guide for Core Data.

Core data migration From multiple versions

Problem
I got 4 versions of core data model (light weight migration)
In version 5 it need to be manually create mapping model(Change data type from nsnumber to nsstring)
migrate from v4 to v5 work fine with mapping model, but for user with different version (1 - 3) how do I handle this migration ?
I have read some SO post Core Data Migration error when migrating between non-consecutive versions and try to using progressive migration mentioned there, but it fail on clean install because in progressivelyMigrateURL:
NSDictionary *sourceMetadata =
[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type
URL:sourceStoreURL
error:error];
if (!sourceMetadata) return NO;
and in - (NSPersistentStoreCoordinator*)persistentStoreCoordinator;
if (![self progressivelyMigrateURL:url
ofType:NSXMLStoreType
toModel:mom
error:&error]) {
[[NSApplication sharedApplication] presentError:error];
return nil;
}
//END:progressivelyMigrateCall
//START:persistentStoreCoordinator
if (![persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType
configuration:nil
URL:url
options:nil
error:&error]) {...}
which make addPersistentStoreWithType: unreachable
Do I need to change anything to made this work ?
Don't sure if I need to do all this work because I have read some comment here stated that core data do all this hard work automatically, but can't find any document yet.

NSPersistentStoreCoordinator - how to handle schema incompatibility errors?

Every time I change the Core Data model for my app, it generates an unrecoverable error at the next startup: "The model used to open the store is incompatible with the one used to create the store".
The only reliable way to avoid this I have found is to manually delete the app and let Xcode reinstall it, or use other techniques to manually blow away the Core Data store .sqlite store file. This is obviously not viable for shipping out to users.
Apple's default App Delegate template for initializing the NSPersistentStoreCoordinator includes this comment:
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
TODO: Replace this implementation with code to handle the error appropriately.
...
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
*/
I have not been able to find any sample or instructions on how to "handle the error appropriately" though.
I would be happy to simply blow away the database in this case and let the app regenerate it from online data. This is what I do when I blow the database away manually. But how can I do that automatically in response to this error condition?
Are there any good samples explaining best practices?
Best way is to use lightweight migration. See Apple documentation: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmLightweightMigration.html.
When you need to make changes in model in new app version, you create new model. You do this in Xcode - just select Your current model and select from menu Editor/Add model version… Without this, automatic migration will not work.

Resources