Migration issues with UIManagedDocument - core-data

I started using CoreData in my application following Stanford CS193P lessons regarding the use of iOS 5's new class UIManagedDocument. The approach itself is quite straightforward but I can't understand how to deal with model modifications i keep making. This is how I instantiate my UIManagedDocument object (inside the appDelegate, so that every other class can use it):
if (!self.database) {
NSURL *url=[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"AppName"];
UIManagedDocument *doc = [[UIManagedDocument alloc] initWithFileURL:url];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
doc.persistentStoreOptions = options;
self.database=doc;
[doc release];
}
The issue I have is that every time I change even a little bit of my .xcdatamodel, I am unable to get all the content previously stored in the document as well as to create any new instance. As a matter of fact doing this generates the following exception:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.'
I thought setting the "options" property of the managed document would have solved the problem, but apparently this isn't enough.
Anyone can help? Couldn't' find other questions that actually fit my precise needs.

Before you modify your Core Data model, you should "Add Model Version".
1.
Select the original model file. (e.g. YourProject.xcdatamodel)
2.
"Editor" -> "Add Model Version...". Then add a new model version (e.g. 2.0)
3.
You will get a new model file. (e.g. YourProject 2.0.xcdatamodel). Modify it.
4.
Change the current model version. Select the top .xcdtatmodel file -> "View" -> "Utilities" -> "Show File Inspector". Find the tag "Versioned Core Data Model" and choose the right version you want to modify.
It also disturbed me for a long long time. Hope this way can help you ^^

There is a really simple solution to your problem. Simply erase the app from the simulator or device (touch the app icon for a few seconds, and touch the cross that appears when the app icons start wiggling). That way, Xcode updates your app's UIManagedDocument.

Make sure that you did not mistype NSDocumentDirectory as NSDocumentationDirectory.
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSDocumentDirectory is right!

Related

iOS Core Data App Delegate?

I am working on a app that is going to require the use of Core Data and I can't help but notice that Core Data has to be put in manually if you use anything but the Master-Detail, Utility or Blank templates in Xcode.
I also noticed that in order for Core Data to work properly, you HAVE to have your app wrapped in a Navigation Controller and Core Data's code in the AppDelegate files.
Anyone know of a way around this or is this the way it is supposed to be?
My App Delegate looks something like this and those three lines seem to be the most important setup for the ManagedObjectContext!
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
FBBetsViewController *controller = (FBBetsViewController *)navController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
Those templates include some core data setup, but it is far from mandatory. You can use core data from within any project. If you want, you can just take the code from the empty application, and use it in your project.
If you look in the generated code, you will see three "getters" for the three main components used to build the core data stack.
managedObjectModel creates the model, by using the model file from your bundle. Create that easily in Xcode by New-File and selecting the Core Data Data Model.
persistentStoreCoordinator uses the model, and a SQL store.
Finally, managedObjectContext is created by using the persistentStoreCoordinator. Note, you really can build that stack in one method if you want. There is no requirement to have those individual accessors...
You could do something like this...
- (NSManagedObjectContext*)setupCoreDataStack
{
// Load the model description
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"APPNAME" withExtension:#"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// Prepare the persistent store coordinator - needs the model
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [applicationDocumentsDirectory URLByAppendingPathComponent:#"APPNAME.sqlite"];
NSError *error = nil;
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
// Handle the error !!!!!
// exit the function
return nil;
}
// Create the managed object context. This is what you will really
// use in the rest of your program.
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc setPersistentStoreCoordinator:psc];
return moc;
}
And now you have almost the same stack as the others. The only real difference is that the MOC here is using the main queue concurrency type, which is a much better alternative.
If you want to have a much better performance model, insert a parent moc.
Actually, if you are not married to a current cored data strategy, I'd suggest UIManagedDocument.
Core Data does not impose that you use a navigation controller nor that you set it up in the AppDelegate. It's customary to put the setup in the AppDelegate upon startup but really, you can move it wherever you want as long as you make sure it's only initialized once.

UIManagedDocument migrate data model

I am working on an iPhone app that uses a subclass of UIManagedDocument and stores its documents on iCloud.
It was all working fine until I changed my core data model / scheme (adding a new model version - like I had several times in the past few weeks).
I added a new property and changed the data type of one of the existing properties.
Now when I run my app I don't seem to be able to load my documents with UIManagedDocument's -openWithCompletionHandler:.
I can create new documents and read/write those.
If I change the data model version back 1 then I am able to read the existing docs, but not the new ones.
From what I understand I am only do lightweight migrations to the data model and UIManagedDocument is supposed to handle that right?
Any advice would be greatly appreciated!
Given below is based on my understanding:
NOTE - I haven't tried it for iCloud but I have tested it for non-icloud and seems ok.
UIManagedDocument configures the managedObjectModel and a Persistent Store Coordinator by itself
When migration needs to be done, just set the UIManagedDocument's persistentStoreOptions
//Note - In this example, managedDocument is a UIManagedDocument property
self.managedDocument.persistentStoreOptions = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
Refer:
Apple Documentation on Core Data Versioning
Point of using NSPersistentStoreCoordinator?
In a subclass of UIManagedDocument you may want to try overriding managedObjectModel like so:
- (NSManagedObjectModel *)managedObjectModel
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"<ModelNameHere>" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
return managedObjectModel;
}

A Second In-Memory-Store As A Cache

Dear community.
I try to discover opportunity to using 2 persistent stores for improve performance of my application.
What i do here:
CREATE 2 PERSISTENT STORES
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:[NSNumber numberWithBool:YES]
forKey:NSMigratePersistentStoresAutomaticallyOption];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType
configuration:nil
URL:[NSURL URLWithString:#"memory://store"]
options:dict
error:&error])
{
[[NSApplication sharedApplication] presentError:error];
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
return nil;
}
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:url
options:dict
error:&error])
{
[[NSApplication sharedApplication] presentError:error];
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
return nil;
}
ASSIGN new created objects to in-Memory store
NSManagedObject *objectCarrier = [NSEntityDescription
insertNewObjectForEntityForName:#"Carrier"
inManagedObjectContext:managedObjectContext];
[objectCarrier setValue:startForCarrier
forKey:#"name"];
NSURL *url = [NSURL URLWithString:#"memory://store"];
[managedObjectContext assignObject:objectCarrier
toPersistentStore:[[appDelegate persistentStoreCoordinator] persistentStoreForURL:url]];
SAVE FINAL OBJECT
A difference between in-memory and particular persistent store using is
i have wrong using objects from predicates for same code.
If i just change persistent store type, i pickup object:
NSManagedObject *destination = [[codeAfterComparing lastObject] valueForKey:codeRelationshipName];
But set values for this object is doesn't work.
If i try to assignObject for received object, i have error (it's doesnt matter, how this object was save as inMemory or asSqlLite store object, error is start every time).
2011-02-16 14:32:45.037 snow
server[44411:1803] * Terminating app
due to uncaught exception
'NSInvalidArgumentException', reason:
'Can't reassign an object to a
different store once it has been
saved.'
Attempt to save a final object's graph with two different stores gives me error "CoreData does not support persistent cross-store relationships", and it's doesn't matter, where cureent object assing.
Migration was as :
for (NSPersistentStore *persistentStore in [persistentStoreCoordinator persistentStores]) {
if (persistentStore.type == NSInMemoryStoreType) {
// migrate the in-memory store to a SQLite store
NSError *error;
[persistentStoreCoordinator migratePersistentStore:persistentStore toURL:[NSURL fileURLWithPath:[[self applicationSupportDirectory] stringByAppendingPathComponent:#"storedata.sql"]] options:nil withType:NSSQLiteStoreType error:&error];
if (! newPersistentStore) {
Product error: "Can't add the same store twice"
So, the result is a very strange for me:
1. Looks like managed object context have no difference for objects between 2 stores. If i ask save, it take whole object and save so same sqlite store
2. maybe a way to using different persistent store coordinator's but i don't know exactly, how is easy transfer objects between 2 stores. Of course, i can do a copy (include relationships e.t.c.) but this is a hard code for this simple issue, i guess.
Maybe somebody can suggest about my code wrong or good working examples of code to review and understand a good way to do in memory cache with core data? Google search gives not too much examples.
If you look at the Core Recipe example code on Apple's website, they use multiple stores to save objects in memory and on disk.
Thought I'd take a stab here.
I've had this problem in the past. I ended up removing the functionality for two persistent stores in the same coordinator. If I understand Apple correctly, Object Entities cannot be shared between persistent stores. So to make things easier, I usually just do the following (though I suspect there is an efficiency issue with using an additional Coordinator)
1 NSPersistentStore per NSPersistentStoreCoordinator
break up the scratchpad work to the NSManagedObjectContexts
create a deep-copy method to your NSManagedObject subclasses
And then, when whatever class you have managing each persistent store utilize the copy function to import the managed objects.
I can't really think of an instance where you'd want to go through the extra trouble of individually assigning the managed objects to a specific store that wouldn't be taken car of in this way.
I have a program that utilizes two stores - one in memory for transient objects and another managing the document. It's working just fine.
In iOS 5 Apple introduce Nested Managed Object Contexts where you can work with two Managed Object contexts.
This may replace your approach with the in memory store because e.g. you can now use one of the (also) new concurrency types to run one context in the background (e.g. for background fetching) and another as your main context.
Take a look in the WWDC2011 Session 303.

NSManagedObjectModel versionIdentifiers

The documentation for NSManagedObjectModel -versionIdentifiers says,
The Core Data framework does not give models a default identifier, nor does it depend this value at runtime. For models created in Xcode, you set this value in the model inspector.
I am not sure, but I think that setting version identifiers might help me as I go about coding model migration policy classes. Does anyone know how one might set these identifiers in Xcode? I have poked around a fair bit without success.
Thanks.
Okay, well this approach did not end up being helpful for me. I solved my Core Data migration debugging problems with the following code:
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"Spark.sqlite"]];
NSError *error = nil;
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:storeUrl
error:&error];
if (!sourceMetadata)
NSLog(#"sourceMetadata is nil");
else
NSLog(#"sourceMetadata is %#", sourceMetadata);
On the other, I just now figured out the answer to my original questions, fwiw.
If you go to your project window and select a .xcdatamodel file and 'get info', then most of the time you will get a 'File "Xxx.xcdatamodel" Info' window - with 'General', 'Targets', 'Build' and 'Comments' tabs. (Yes, the "most of the time" part has me confused.)
However, if you then select your .xcdatamodel file again and 'get info', you will (probably) get a very different inspector - one called 'Data Model "Xxx" Info'. This window has two tabs: 'Appearance' and 'Versioning'. The Versioning tab lets you set the Model Version Identifier.
Bottom line: Setting the Model Version Identifier in Xcode is akin to making your way to Platform 9 3/4 - but unlike 9 3/4 once you get there, it's not really clear why you'd want to be there.

What do I have to do to get Core Data to automatically migrate models?

I have read the documentation about automatic /lightweight migration for Core Data models - but I am having problems implementing it.
As I understand it the application should notice that the model it has and the model that exists on a device already are not the same. If you have only added attributes or relationships and similar simple changes then the model should be upgraded automatically.
Any pointers - do I need to set something in Xcode?
I've now found out that this is quite simple - once you know where to look.
In my AppDelegate I set-up the NSPersistentStoreCoordinator - and you need to add some options to this to tell it to handle auto-migrate:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Handle error
NSLog(#"Problem with PersistentStoreCoordinator: %#",error);
}
Then you need to do a little trick in xCode:
Select your xcdatamodel file
Select the Design Menu at the top - then Data Model - then choose Add Model Version
Your xcdatamodel file will then get moved into a new directory with the same name as your xcdatamodel file but with the extension xcdatamodeld - there will be a second file in this directory with a 2 in the name. Select the new file and then Design->Data Model->Set Current Version (in Xcode 4 you do this)
If you have already made the changes that have caused your project to be incompatible - take these changes out of the original xcdatamodel file. If you have yet to make the changes - then just edit the 2.xcdatamodel file (the one you just made current version).
Now when you install this version onto a device that has the old model - it will automatically upgrade that model to the new model.
This seems great and as simple as I wanted - but I think you need to be careful during development as you change a model - otherwise you will have to create a new version for each change.
I think what I will do is that I will keep all of the changed files and then once I get ready to deploy my update I'll delete all the in-between files and just deploy with the oldest and latest models.
UPDATE (15/07/2011):
Thanks to #rockstarberlin for pointing out there is updated documentation at apple:
Xcode 4: Setting a Managed Object Model’s Current Version
Update: 8/19/2013 better link:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmModelFormat.html
This was incredibly helpful. The Apple documentation was -- as usual -- woefully incomplete. I recommend doing a clean build, as I ran into an error "Can't merge models with two different entities xxx" when I first ran after making these changes. The clean build fixed it up.
Grouchal's answer is perfect...but if you are still having the "Can't merge models with two different entities xxx" even after cleaning up the build several times...Your might have issues with how the managedObjectModel is being loaded...take at look at this one...which helped me fix it..
core data migration problems
Also, if you stumbled upon this post, like I did, after getting the "The model used to open the store is incompatible with the one used to create the store" error and you are just debugging using the simulator and wanting to completely replace the old model installed, you can just Reset the Simulator app or deleting your app from the simulator would probably work as well.
It didn't occur to me to try this until reading the posts here, at which point I realized that I had installed the app in the simulator and then subsequently changed the model, causing the aforementioned run-time error.
To follow up on Santthosh's answer, figured I'd post the code snippet right here instead. You need to create your managedObjectModel with initWithContentsOfURL: instead of mergedModelFromBundles: otherwise you'll get error:
Can't merge models with two different
entities XXX and XXX
If your Model file is named "Model", here's how you create the managedObjectModel:
NSString *path = [[NSBundle mainBundle] pathForResource:#"Model" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
Credit to this blog post.
the menu in Xcode 4 changed a bit. here´s a description how to do it in Xcode 4:
Xcode 4: Setting a Managed Object Model’s Current Version
I've had this issue for years, and I tried all of these answers to no avail. Today I finally figured out what I was doing wrong. Very simple problem, but I overlooked it. When creating a newer version of the data model, if you are ADDING columns make sure to mark them as OPTIONAL. If you do not the simple migration will not work because the new column values will not be filled in.
As soon as I made sure my new columns has "optional" checked, I tried the migration again and it worked.
I stumbled onto this post because of a different problems, but the error was "The model configuration used to open the store is incompatible with the one that was used to create the store."
Here was my problem and the solution to it. In my model, I was using configurations. I had some of the entities being stored in one file and the others in a second file. (I have some defaults that might periodically need to get downloaded, and it would be an incredible pain to merge them into the whole). Anyhow, I made a new entity. The program seemed to run fine, but whenever I'd quit, I got the above error.
The solution there was to look at my configurations, realize that I had an entity that wasn't currently in any of the configurations, and add it to one. Runs like a dream.
This won't fix the OP's problem. But maybe some frustrated person who lands here via google will be in the boat I was in :)
iOS 4.0+
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"model" withExtension:#"momd"];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
Minor edit to #Grouchal's awesome instructions above for Xcode version 5:
Old:
2. Select the Design Menu at the top - then Data Model - then choose Add Model Version
Version 5+:
2. Select the Editor menu, then Add Model Version…, type your Version name and Based on model (select your original model from the list)

Resources