Core data migration From multiple versions - ios

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.

Related

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.

iOS application coreData with PersistentStore as static sqlite Data Migration?

The app is using a static sqlite(initial data) from bundle directory as a persistent store for Coredata. The sqlite has 7 tables of which one table is modified by adding an extra column/field. How does I make coreData understand that the persistent store(store) is changed and it needs to take the new update ?
Is there any Model version concept for sqlite like we do for coredata ?
For those who doesn't want to dig into documentation and is searching for a quick fix:
1>Open your .xcdatamodeld file
2> click on Editor
3> select Add model version...
4> Add a new version of your model (the new group of datamodels added)
5> select the main file, open file inspector (right-hand panel)
6> and under Versioned core data model select your new version of data model for current data model
7> THAT'S NOT ALL ) You should perform so called "light migration".
8> Go to your AppDelegate and find where the persistentStoreCoordinator is being created
9> Find this line if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
10> Replace nil options with #{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES} (actually provided in the commented code in that method)
Here you go, have fun!
P.S. This only applies for lightweight migration. For your migration to qualify as a lightweight migration, your changes must be confined to this narrow band:
Add or remove a property (attribute or relationship).
Make a nonoptional property optional.
Make an optional attribute nonoptional, as long as you provide a default value.
Add or remove an entity.
Rename a property.
Rename an entity.
If you are just adding attributes to an entity, you can use the coredata lightweight migration suggest on Apple documentation
NSError *error = nil;
NSURL *storeURL = <#The URL of a persistent store#>;
NSPersistentStoreCoordinator *psc = <#The coordinator#>;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
BOOL success = [psc addPersistentStoreWithType:<#Store type#>
configuration:<#Configuration or nil#> URL:storeURL
options:options error:&error];
if (!success) {
// Handle the error.
}

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.

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