NSManagedObjectModel versionIdentifiers - core-data

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.

Related

Core Data: NSObjectID and NSTemporaryObjectID leaks

Before I send my app to the App Store I like to check it for memory leaks and other fishy stuff with instruments. There is one Core Data issue that I can't seem to solve, so I've decided to create a small test app to illustrate the problem.
What's the problem?
When I save an entity in a (child) NSManagedObjectContext it is propagated to its parent NSManagedObjectContext. During this process Core Data creates internal instances of _NSObjectID and NSTemporaryObjectID. For some reason these instances are left behind and the only way to get rid of them is to reset the parent NSManagedObjectContext.
My app is of course a lot more complex than this little test app and resetting the NSManagedObjectContext isn't an option for me.
Test app
The test app is a standard iOS app based on the single view template with the CoreData option checked. I've used objective-c to keep it similar to my production app.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize the Core Data stack
self.persistentStoreCoordinator = [self persistentStoreCoordinator];
// Create the a private context
self.rootContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.rootContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
// Create a child context
self.childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.childContext.parentContext = self.rootContext;
// Create a person
[self.childContext performBlockAndWait:^{
Person *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.childContext];
person.name = #"John Smith";
person.age = 30;
// Save the person
[self.childContext save:nil];
// Save the root context
[self.rootContext performBlockAndWait:^{
[self.rootContext save:nil];
}];
}];
return YES;
}
When you run the code above with instruments and the allocations instrument you can see that Core Data leaves some stuff behind.
You can find the full project here: https://github.com/Zyphrax/CoreDataLeak
Things I've tried
I've tried things like [context refreshObject:... mergeChanges:YES], adding #autoreleasepool and/or [context processPendingChanges] inside the blocks, it all doesn't help. The only way to get it clean is to do a [context reset] (sledgehammer approach).
It's hard to find other people reporting this problem.
This blog post seems similar:
http://finalize.com/2013/01/04/core-data-issues-with-memory-allocation/
I hope you guys can help me with this.
Here is what I see, which is very similar to yours...
However, I don't know that I would be concerned, unless you see lots of these, and they never go away. I assume the internals of Core Data (including the row cache has) some sort of object caching going on.
On the other hand, my Core Data usage has changed a bit over the past year or two.
Unless it is a very simple app, I almost never create new objects in a child context. I will fetch and modify them, but if I end up creating a new object, I make sure that is done in a sibling context.
However, if you modify your code slightly, by adding this line (with your appropriate error handling - it returns BOOL) before the initial save...
NSArray *inserted = self.childContext.insertedObjects.allObjects;
[self.childContext obtainPermanentIDsForObjects:inserted error:&error];
you should get something like this instruments report, which shows all objects created as being transient...
Thus, I don't necessarily think it is a permanent leak, because once I force the context to convert to a permanent ID, the objects go away. However, who knows how long they keep those object ID objects cached.
In general, when I create objects in a context that contains a hierarchy, I will always obtain permanent IDs first (for many reasons). However, as I said earlier, I usually create new objects in a context that is directly created to the persistent store (because I have had to deal with other issues related to hierarchies temporary object IDs, especially when using multiple non related contexts).

Migration issues with UIManagedDocument

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!

Attempting to create USE_BLOCK_IN_FRAME ... EXC_BAD_ACCESS with NSFetchedResultsController

This is an update to my problem. I am receiving this warning now when the program aborts.
warning: Attempting to create USE_BLOCK_IN_FRAME variable with block that isn't in the frame.
I can't find much information on what this means.
This has me baffled. I get the EXC_BAD_ACCESS error. I have NSZombieEneabled (which helped with an earlier problem), but there is no call stack to trace.
I have some nearly identical code that is working with respect to another fetched result controller.
This seems to have something to do with the relationships between the job entity and its associated client entity. The relationship is [job entity] <<--> [client entity].
Initially, I see that the code works without error where the job entity corresponding to the selected row has no client entity linked through a relationship. So in the case where it fails, this points to a client entity, but when it doesn't fail, the pointer is nil.
When I encounter this problem, I start the application and go directly to the job picker view and select a cell. It's at that point that the problem occurs.
I did an experiment by starting the application and going to the client picker view first, knowing that a fetch would occur of all of the client entities. Then I went to the job picker view and selected a cell. The problem did not occur.
Since I am just trying to pass a pointer to a job entity that was already fetched, I don't understand what's happening.
By the way, the code was working fine before I switched to using NSFetchedResultsControllers. I like what they can do for me, but there are some dynamics going on here that I haven't figured out.
The logging is not showing me anything I understand toward resolving the problem.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
userState.selectedJob = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"\n\n(1 Pick) indexPath: %#\n",indexPath);
NSLog(#"\n\n(1 Pick) userState: %#\n",userState);
NSLog(#"\n\nnumber of Objects in job fetchresultscontroller = %d", [[fetchedResultsController fetchedObjects] count] );
NSLog(#"\n\n(1 Pick) selected job: %#\n",[self.fetchedResultsController objectAtIndexPath:indexPath]); // This line is causing the problem...
NSLog(#"\n\n(1 Pick) selected job: %#\n",userState.selectedJob); // Omitting the line above, this line fails
[self.navigationController pushViewController:userState.jobInfoTVC animated:YES];
}
The debug output is
2011-05-07 09:27:04.142 job1[6069:207]
(1 Pick) indexPath: <NSIndexPath 0x5952590> 2 indexes [0, 3]
2011-05-07 09:27:04.142 job1[6069:207]
(1 Pick) userState: <UserStateObject: 0x5919970>
2011-05-07 09:27:04.143 job1[6069:207]
number of Objects in job fetchresultscontroller = 4
(gdb)
The final code should be as simple as this, which led me to all of the logging:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
userState.selectedJob = [self.fetchedResultsController objectAtIndexPath:indexPath]; // Original failure was at this line
[self.navigationController pushViewController:userState.jobInfoTVC animated:YES];
}
I use the singleton userState to keep track of what the user has done. So I keep last selectedJob and selectedClient entity pointers there. This has worked okay before I switched to NSFetchedResultsController.
I have also had a problem with Attempting to create USE_BLOCK_IN_FRAME variable with block that isn't in the frame. Although mine wasn't anything to do with NSFetchedResultsControllers.
Possibly Your Problem
I noticed you mention you are using a singleton, so maybe your problem is solved at this link:
http://npenkov.com/2011/08/01/solving-issues-like-warning-attempting-to-create-use_block_in_frame-variable-with-block-that-isnt-in-the-frame/
From the link:
Exactly in session manager I used the macro, before #synthesize – this was the problem, static definitions should not appear before synthesized methods. So if you have something like:
SYNTHESIZE_SINGLETON_FOR_CLASS(SessionManager)
#synthesize loggedUserId, ...
Just replace it with:
#synthesize loggedUserId, ...
SYNTHESIZE_SINGLETON_FOR_CLASS(SessionManager)
enter code here
My problem
The problem I had was to do with duplicating a variable declaration, in this case the variable inherited from NSManagedObject :
- (void)functionThatDoesSomething {
VariableInheritedFromNSMObj *variableA = nil;
for (VariableInheritedFromNSMObj *variableA in [self containerObject]) {
NSLog(#"a = %#\n", [variableA name]);
}
[variableA setName:#"StackOverflow"];
}
Moving the first line, where variableA is initialised to nil, to after the loop fixed the problem. In my production code I then changed the name of one of the variables.
Hope that helps you or someone else who comes across this problem. This error seems to manifest itself in many different ways.
Is the deployment target OS version set to a lower one than you have debugging support for in Xcode 4.2? When this happened to me, the deployment target had somehow changed to 4.1, but I only had the 4.3 simulator without the additional debugging support for 4.0-4.1.
There are ways to fix this:
Install the debug symbols for your lower OS version from the Xcode preferences. Go to downloads, components and install OS 4.0 - 4.1 Device Debugging Support (or earlier if you need it).
Set the deployment target to 4.3 or higher.
If you choose the former, it's probably worth ticking the box in preferences to download the updates automatically.

Can't solve, "Can't merge models with two different entities named"

I'm working with a Core Data project in iOS 4.1 (targeting 3.1). When I add a Data Model version I get the dreaded "Can't merge models with two different entities named xxx" error." Cleaning Targets does not help. Deleting the build directory does not help. The only thing that solves the issue is deleting the previously installed version of the app and installing fresh, which defeats the entire purpose of versioning and data migration.
I've got another project that's successfully using this process and they share the same code libraries I've put together for handling Core Data. I cannot figure out what could be hanging up the one project.
I based my Core Data code on, Grouchal's answer on this link text and Jeff Lamarche's link text. In troubleshooting I've poured over these as well as other, similar articles on the net. Most people with this issue seem to have had good luck with "Clean All Targets." But I'm striking out.
Any suggestions?
For those who come across this question after trying to use core data lightweight migrations:
I was having this issue even after following the instructions for creating a new version of my data model. I noticed that there were two ".mom" files in my application bundle, one ".mom" and one ".momd" directory that contained ".mom" files.
Based on that, I was able to find this excellent post explaining the issue and offering a solution.
The key is to replace the implementation of - (NSManagedObjectModel *)managedObjectModel that is generated for you with this implementation:
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"Foo" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
return managedObjectModel; }
where 'Foo' is the name of your data model.
Hopefully this is useful to someone - I spent WAY too many hours beating my head against the wall on this. Thanks again, Apple! :)
Here's my solution
((RootViewController *) [self.tabBarController.viewControllers objectAtIndex:0]).managedObjectContext = self.managedObjectContext;
((AlbumViewController *) [self.tabBarController.viewControllers objectAtIndex:1]).managedObjectContext = self.managedObjectContext;
((CameraViewController *) [self.tabBarController.viewControllers objectAtIndex:2]).managedObjectContext = self.managedObjectContext;
((VideoViewController *) [self.tabBarController.viewControllers objectAtIndex:3]).managedObjectContext = self.managedObjectContext;
I discovered that a 3rd party library I was using was creating it's own CoreData data store and was using the "merge models" method or data model management.
I contacted the vendor and the helpfully fixed the situation.

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