I've been hitting this problem with coredata and it's driving me nuts because it should be straight forwards
I'm currently working on the first release of this app, os obviously I keep tweeking the core data model here and there,
However each time change the core data model I need to uninstall the application and reinstall the new version.
This is passable while its just me, but once released I need to be able to change update the app without my users reinstalling.
What am I missing,
Is there some code I need to write to tell core data how to modify the existing persistant data to the new one ?
THanks for your help
Jason
Core data model - migration - adding new attributes/fields to current data model - no RESET of simulator or app required
Steps:
Create Model version from editor - Give it any meaningful name like ModelVersion2
Go to that model version and make changes to your model.
Now go to YourProjectModel.xcdatamodeld and set current version to newly created version.
Add below code to place where you are creating persistent coordinator -
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
and set options value as options for method -
[__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]
In my case, it looks something like this:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (__persistentStoreCoordinator != nil) {
return__persistentStoreCoordinator;
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"LGDataModel.sqlite"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (! [__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return__persistentStoreCoordinator;
}
Link: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmInitiating.html#//apple_ref/doc/uid/TP40004399-CH7-SW1
You need to read up on Core Data versioning and migration. Here's a blog post that explains it well:
http://www.timisted.net/blog/archive/core-data-migration/
Related
I have changed attribute type of my data model from Int16 to Int64. Is migration required for it or it will automatically work as its the same data type Int. Please guide.
Yes you can change attribute type and migrate your datastore in core-data but for that while creating/configuring your NSPersistentStoreCoordinator you need to set few option which i have mentioned below. This is call LightWeight Migration in core-data that we are doing here.
Update your persistentStoreCoordinator initialiser method with following code.
- (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]];
// *** add support for light weight migration ***
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"StoreName.sqlite"];
NSError *error = nil;
// *** Add support for lightweight migration by passing options value ***
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
// 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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
You can read more about core-data migration at following sites.
1. Apple official documentation
2. Other site
in my app I've used Core Data Automatic Lightweight Migration fine for multiple versions. About a year ago I implemented iOS File Sharing so I moved the sqlite database to the /Library folder rather than the /Documents directory to keep it hidden. I haven't added another Core Data version since that time.
Now when I try to add another Entity and increase the model version I get the following error (truncated to show just the tail end):
"_NSAutoVacuumLevel" = 2;
}, reason=Can't find model for source store}
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason:
'This NSPersistentStoreCoordinator has no persistent stores.
It cannot perform a save operation.'
I've verified that the .sqlite file exists on disk as expected. I've been troubleshooting for a few days and it seems if I move the sqlite file back to the /Documents directory the migration works OK.
Below is the code for the persistent store coordinator:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self libraryDocumentsDirectory] stringByAppendingPathComponent: #"myappdb.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
//Pass options in for lightweight migration....
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]) {
/*Error for store creation should be handled in here*/
NSLog(#"ERROR IN MIGRATION........%#", error);
}
return persistentStoreCoordinator;
}
- (NSString *)libraryDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
}
Has anyone encountered this situation before?
Thanks in advance for your help.
"Now when I try to ad another model entity and increase the version number..."
You need to create a new model version before you create a new entity or make changes to existing entities. Make sure your changes were not made in the old model version by mistake.
After creating version and marking it as a current version you have to add an extra value in options which is used to add PersistentStore and then you go( I am not sure about other iOS version but yeah it will definitely work on iOS 7).
-(NSManagedObjectModel *)managedObjectModel
{
if (managedObjectModel != nil)
{
return managedObjectModel;
}
managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return managedObjectModel;
}
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (persistentStoreCoordinator != nil)
{
return persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"ABC.sqlite"];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] ini tWithManagedObjectModel:[self managedObjectModel]];
//Creating Lightweight migration.
NSDictionary *options =
#{
NSMigratePersistentStoresAutomaticallyOption:#YES
,NSInferMappingModelAutomaticallyOption:#YES
,NSSQLitePragmasOption: #{#"journal_mode": #"DELETE"}
};
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}
I am using core data in my iOS project. I am using light weight migration for migrating schema between datamodel versions.
I had version 3.5 set as current modal version for the app which is released in field. Before making new changes to the datamodel schema, I have created a new datamodel version 3.6 and added a new entity and couple of attribute updates on the new datamodel.
After completing all changes, I have created a new mapping model for 3.5 to 3.6
And I am using the following code for the light weight migration,
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil)
{
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:PERSISTENT_STORE_FILENAME];
NSDictionary *storeOptions = nil;
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:storeOptions error:&error])
{
BOOL shouldPerformLightWeightMigration;
if (self.optionalCoreDataMigrationHandler) {
shouldPerformLightWeightMigration = optionalCoreDataMigrationHandler();
}
if (shouldPerformLightWeightMigration) {
[self performLightweightMigrationInBackgroundWithStoreURL:storeURL];
}
}
return _persistentStoreCoordinator;
}
- (void)performLightweightMigrationInBackgroundWithStoreURL:(NSURL *)storeURL {
[self.initializerDelegate willStartMigration];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void){
NSDictionary *storeOptions = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:storeOptions error:&error]) {
dispatch_async(dispatch_get_main_queue(), ^(void){
[self.initializerDelegate didFinishMigrationSuccessfully:NO];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^(void){
[self.initializerDelegate didFinishMigrationSuccessfully:YES];
});
}
});
}
When I installed the new app on top of existing app, the app became blank and did not retrieve any data and I have encountered the following error when NSManagedObjectContextDidSaveNotification is called,
An observer of NSManagedObjectContextDidSaveNotification illegally threw an exception. Objects saved = {....}
and exception = Object's persistent store is not reachable from this NSManagedObjectContext's coordinator with userInfo = (null)
Same process usual works for me in another app. But for some reason it did not work here.
In past, I had similar issue in the other app. Datamodel migration from v1 to v2 failed, so I have created a new model version v3 with v1 as source, re-did all the datamodel updates on v3 and created a new mapping model from v1 to v3. Then the lightweight migration worked.
In current scenario, I am not in a position to follow the above approach.
Please help in resolving the current migration issue and any other best practices to avoid such problems in future, as I often make updates to the datamodel.
I am working on an application that incorporates iCloud syncing of its Core Data store (simple application with a single store/model/context). Since the store also contains image data, it has the possibility for getting quite large and so I would like to add a setting to allow the user to disable the syncing if they wish. I have looked at some sample code for using Core Data in both scenarios, and it looks to me that the only real difference between running with iCloud enabled and disabled are the options passed to the NSPersistentStoreCoordinator when it is added. As such, I had though about doing something like this:
NSPersistentStoreCoordinator *psc;
NSDictionary* options;
//Set options based on iCloud setting
if ([enableSwitch isOn]) {
options = [NSDictionary dictionaryWithObjectsAndKeys:
#"<unique name here>", NSPersistentStoreUbiquitousContentNameKey,
cloudURL, NSPersistentStoreUbiquitousContentURLKey,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
} else {
options = nil;
}
//Add the coordinator
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
//Handle error
}
So, I have a couple of questions about the above:
Is my assumption correct, or is there more between the two states that needs to be different?
Often in examples this code is called in the application delegate, so it usually is only called once per application run. Is there a good strategy to responding to the necessary change on demand as the user toggle the setting?
Thanks!
This is the solution that I came up with, and it is working pretty well. Basically the code below is placed in a method that you can call when your application starts up, or anytime the your user-facing "Enable iCloud Sync" setting changes. All that needs to change between the two operation modes is the NSPersistentStore instance, the underlying model file and the coordinator need no changes.
My application only has one persistent store to worry about, so upon calling this method, it just removes all stores currently attached to the coordinator, and then creates a new one with the appropriate options based on the user settings.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"DataModel.sqlite"];
NSError *error = nil;
NSArray *stores = [__persistentStoreCoordinator persistentStores];
for (int i=0; i < [stores count]; i++) {
[__persistentStoreCoordinator removePersistentStore:[stores objectAtIndex:i] error:&error];
}
//Determine availability. If nil, service is currently unavailable
NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
if (!cloudURL) {
NSLog(#"iCloud currently unavailable.");
}
//Check if user setting has been set
if (![[NSUserDefaults standardUserDefaults] objectForKey:kCloudStorageKey]) {
//Set the default based on availability
BOOL defaultValue = (cloudURL == nil) ? NO : YES;
[[NSUserDefaults standardUserDefaults] setBool:defaultValue forKey:kCloudStorageKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
//Set options based on availability and use settings
BOOL cloudEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:kCloudStorageKey];
NSDictionary *options = nil;
if (cloudEnabled && cloudURL) {
NSLog(#"Enabling iCloud Sync in Persistent Store");
options = [NSDictionary dictionaryWithObjectsAndKeys:
#"<awesome.unique.name.key>", NSPersistentStoreUbiquitousContentNameKey,
cloudURL, NSPersistentStoreUbiquitousContentURLKey,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
}
//Add the store with appropriate options
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
If you have an application that utilizes multiple stores, you may need to keep references around to the stores that you want to enable/disable sync on so you can have a smarter approach rather than just blowing them all away every time.
I added new version to Core Data model. I added new attribute to one entity (Seriese)
But it raise exception
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Can't merge models with two different entities named 'Seriese''
I use the following code:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationPrivateDocumentsDirectory] stringByAppendingPathComponent: #"CoreDataTutorialPart4.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(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}
Any suggestions to solve this error? I don't want to lose the saved data
The issue more than likely comes from how you load you managed object model. The default way is to merge the models in the bundle, however in this case you actually have two models with the same entities (v1 and v2)... it is explained nicely here....Migration Issues
Core Data does not understand that the entity Seriese in both models is intended to be the same entity and that it should translate the attributes of the old Seriese to the new Seriese. Instead, it thinks the new Seriese should be treated as an entirely new entity.
This is usually caused by trying to make changes to the new version that automatic migration cannot handle. Automatic migration can handle changes to attribute names, adding an attribute or other changes that don't affect anything beyond a single entity. Once you begin to change relationships and/or add new entities, you have to do a manual migration.
You can call
+[NSMappingModel inferredMappingModelForSourceModel:destinationModel:error:]
... to test if an automatic migration is possible. If it returns nil and/or and error, then you can't.