Core Data - Lightweight migration doesn't work - ios

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.

Related

Updating Core Data +iCloud model with the next App version

I have to work on the next version of an Application that uses Core Data and iCloud.
iCloud has been activated in the current version using the most "modern" way:
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"data_cfg"
URL:storeURL
options:#{NSPersistentStoreUbiquitousContentNameKey:#"theStore"}
error:&error])
Now I need to update the store structure adding new entities, relationship and so on...
I'm totally stuck because I don't know which is the better way to update a model knowing that iCloud is synchronised with users data.
Which is the best way to perform an update like that? What should I be aware of and what I should pay attention for the most? How to migrate the current data?
Updating a model when using iCloud is the same as when not using iCloud-- with the sole exception that your changes must be ones that work with automatic lightweight migration. Apple's documentation describes the requirements for this kind of migration in detail. The basic steps are:
Create a new version of the data model, and make this version "current". (You must keep the old model around, so now you'll have two, but only one is current).
Make your model changes in the new version.
Add NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption to your options dictionary in the code above, using #YES as the value for both.
Now when you launch the app, Core Data will compare the old and new versions of the model and (assuming your changes work for automatic lightweight migration) modify the persistent store to use the new version.
Keep in mind that iCloud syncing only works between devices that use the same version of the data model. If a user upgrades your app on one device but not another that they use, syncing will stop until they upgrade the app on other device as well.
iCloud Backup doesn't matter, by the way here is solution for installing new sqlite model.
If the data inside the Application can be recreated/downloaded from server, there is a great solution.
You don't need to setup migration stack, there is a very quick solution. The trick is to delete the old sqlite database and create a new one.
Here is the code that I used on my application update.
You need to add this in your AppDelegate.m
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"YourDatabase.sqlite"];
NSManagedObjectModel *managedObjectModel = [self managedObjectModel];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel];
// Check if we already have a persistent store
if ( [[NSFileManager defaultManager] fileExistsAtPath: [storeURL path]] ) {
NSDictionary *existingPersistentStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType: NSSQLiteStoreType URL: storeURL error: &error];
if ( !existingPersistentStoreMetadata ) {
// Something *really* bad has happened to the persistent store
//[NSException raise: NSInternalInconsistencyException format: #"Failed to read metadata for persistent store %#: %#", storeURL, error];
NSLog(#"Failed to read metadata for persistent store %#: %#", storeURL, error);
}
if ( ![managedObjectModel isConfiguration: nil compatibleWithStoreMetadata: existingPersistentStoreMetadata] ) {
if ( ![[NSFileManager defaultManager] removeItemAtURL: storeURL error: &error] )
NSLog(#"*** Could not delete persistent store, %#", error);
} // else the existing persistent store is compatible with the current model - nice!
} // else no database file yet
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error];
return _persistentStoreCoordinator;
}
This code covers issues
if existing database is old one, than deleting and setting up new one.
if there is no database yet (user download only the newest version) than creating new database.
if existing database is compatible with new one, just using that.
Just change the #"YourDatabase.sqlite" to your sqliteDB filename and it will work fine.
If your'e data can't be recreated (synced from server), than You need to setup migration stack
here is link to Apple's guide https://developer.apple.com/library/mac/Documentation/Cocoa/Conceptual/CoreDataVersioning/CoreDataVersioning.pdf

Coredata after Application Update

I'm trying to implement a process for my application that makes the posibility to update the app when needed, and with update I mean donwnload the newer application .ipa in the device.
The thing is that I'm using CoreData to store the server data brought at the first launch, and between the old version and the newer one I've added some entities and atributes for some old entities to the DB. That makes conflicts, as I have no idea how to handle migrations and/or any thing that can provide me the ability to re-create the data base as the structure has changed.
For now, if I update an application with the same DB structure, the app works ok, but if I modify it the app crashes, as expected.
Any thoughts?
If the data inside the Application can be recreated/downloaded from server, there is a great solution.
As I understood You are getting data from server and this is the wonderful case, that means the old data can bee recreated in new database.
You don't need to setup migration stack, there is a very quick solution. The trick is to delete the old sqlite database and create a new one.
Here is the code that I used on my application update.
You need to add this in your AppDelegate.m
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"YourDatabase.sqlite"];
NSManagedObjectModel *managedObjectModel = [self managedObjectModel];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel];
// Check if we already have a persistent store
if ( [[NSFileManager defaultManager] fileExistsAtPath: [storeURL path]] ) {
NSDictionary *existingPersistentStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType: NSSQLiteStoreType URL: storeURL error: &error];
if ( !existingPersistentStoreMetadata ) {
// Something *really* bad has happened to the persistent store
//[NSException raise: NSInternalInconsistencyException format: #"Failed to read metadata for persistent store %#: %#", storeURL, error];
NSLog(#"Failed to read metadata for persistent store %#: %#", storeURL, error);
}
if ( ![managedObjectModel isConfiguration: nil compatibleWithStoreMetadata: existingPersistentStoreMetadata] ) {
if ( ![[NSFileManager defaultManager] removeItemAtURL: storeURL error: &error] )
NSLog(#"*** Could not delete persistent store, %#", error);
} // else the existing persistent store is compatible with the current model - nice!
} // else no database file yet
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error];
return _persistentStoreCoordinator;
}
This code covers issues
if existing database is old one, than deleting and setting up new one.
if there is no database yet (user download only the newest version) than creating new database.
if existing database is compatible with new one, just using that.
Just change the #"YourDatabase.sqlite" to your sqliteDB filename and it will work fine.
If you only added some entities/attributes you can use CoreData lightweight migrations:
1) Add a new version of the schema from the Editor menu with you xcdatamodeld file open.
2) Add the new entities attributes to this new schema version.
3) Set the new schema version as the active one in your xcdatamodeld options (left pane).
4) Set NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption to true in your persistentStore initialization method in the UIApplicationDelegate.
You should now be able to run the app. The schema should be updated automatically to match your new database structure. This will also preserve the database contents, emptying it is probably something you want to do in your code, detecting the first launch of the new version.

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.

iPhone Core Data "Automatic Lightweight Migration"

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...

Resources