Coredata after Application Update - ios

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.

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.

Prepopulated sqlite file in the app bundle. What happens after the existing users update?

I have an app on the store which points to the appname.sqlite file in the document directory.Here's the old code:
NSURL *storeURL = [[self applicationDocumentsDirectory]URLByAppendingPathComponent:#"MyApp.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:#{NSMigratePersistentStoresAutomaticallyOption : #YES,NSInferMappingModelAutomaticallyOption : #YES}; error:&error]) {
abort();
}
I now want to give an update. The new feature being a prefilled .sqlite database which is there in the bundle.See the following
NSURL *storeURL = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:#"MyNewPrefilled" ofType:#"sqlite"]];
It would work ok on the fresh installation the app.But what happens to the users who have logged in and have a lot of data saved in the db (old sqlite in the app directory)? I would lose it because I now am pointing to the .sqlite in the app bundle (MyNewPrefilled.sqlite) and not the app directory one. How do I get the old data back from the old sqlite file of doc directory to my new one in the bundle?
FYI: I use code data migration already (involving version numbers in datamodels) which works ok when i change datamodel in updates.
Apple recommends to use two persistent stores (in the same model) to separate data that is user generated and data that is static. Presumably, if your data is static, you should do exactly that and have a read-only store for the static data. See for example Configurations in the Core Data Programming Guide.
Another approach is
to copy the bundle seed database into the documents directory, and then
read the data from the old store and
copy it to the new one.
Finally, you can delete the old store.

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

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.

iPhone core data preset

What is the best way to initialize Core Data database with content. My iPhone app will have a static database with products (data and images). How/where to store images, how to pre-populate database?
Here is what I did:
Create the database inside the iPhone app
I created the model in XCode and do a query against the database (this creates the database)
My static data is a CSV file
Use a Ruby script to read the CSV file
Use the ruby gem sqlite3 to insert data into the database
Copy back into project
Alternative:
Store the CSV/XML file containing data inside the app
Parse it on startup and create your NSMAnagedObjects
Tools/Resources:
Base software for editing/viewing a sqlite3 database
Database Location:
I'm afraid I don't remember it on the top of my head but when you use the simulator your application will be built and copied into a directory. I think the path to your application will be something like this. The database depending on how it is setup is usually in the Documents folder.
~User/Library/Application Settings/iOS Simulator/<version>/<app id>/
Reference the official Apple docs provide a way to prepopulate data into Core Data:
Core Data Books
Within my own application, I replaced the function 'NSPersistentStoreCoordinator' within AppDeledate with this one :
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CoreDataBooks.CDBStore"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:[storeURL path]]) {
NSURL *defaultStoreURL = [[NSBundle mainBundle] URLForResource:#"CoreDataBooks" withExtension:#"CDBStore"];
if (defaultStoreURL) {
[fileManager copyItemAtURL:defaultStoreURL toURL:storeURL error:NULL];
}
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
/*
Replace this implementation 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.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
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:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
Hope this works, good luck!

Resources