I set up an Entity using the Xcode .xcdatamodel file editor. I created an entity called Person, added a few attributes, then generated a .m file to represent it. That all works fine.
Now when I go to write a line of code like:
Person * person = (Person*)[NSEntityDescription
insertNewObjectForEntityForName:#"Person"
inManagedObjectContext:managedObjectContext];
And I get:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an NSManagedObjectModel for entity name 'Person''
I followed the Location example exactly though, step-for-step I believe, but I think I must have missed some kind of crucial "registration" step where I tell Xcode that my Person entity should be accessible.. Also I didn't have a way to "initialize" the managedObjectContext at all, the Location example doesn't seem to do that either.
The fact that you didn't set up the MOC is almost certainly the problem. Most specifically, it means you're probably not loading your MOM (Managed Object Model) that defines Person. Somewhere in your code you should have something like this:
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
And something like this:
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
And something like this:
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
I'm just copying lines out of the AppDelegate of the Core Data template (what you get if you make a new application that uses Core Data).
If you have all that, make sure that your xcdatamodel is listed in your Compile Sources step of the build. And of course make sure that Person is actually the Entity name in your xcdatamodel. Entity name is not the same as Class, though they are often set to be the same.
You need the init of the Core Data
-(void)initCoreData{
NSError *error;
//Path to sqlite file.
NSString *path = [NSHomeDirectory() stringByAppendingString:#"/Documents/Level4.sqlite"];
NSURL *url = [NSURL fileURLWithPath:path];
//init the model
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
//Establish the persistent store coordinator
NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];
if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error]){
NSLog(#"Error %#",[error localizedDescription]);
}else{
self.context = [[[NSManagedObjectContext alloc ] init ] autorelease];
[self.context setPersistentStoreCoordinator:persistentStoreCoordinator];
}
[persistentStoreCoordinator release];
}
You should check if the NSManagedObjectContext object is nil.
e.g.
if (self.managedObjectContext == nil) {
NSLog(#"NSManagedObjectContext is nil");
return nil;
}
Related
I'm trying to use Core Data on a project with code adapted from iOS Programming: The Big Nerd Ranch Guide (3rd ed). Opening the SQLite file is causing an exception, and no amount of documentation reading or search engine digging is helping me figure out what it means or how to avoid it.
The code in question is in the init method of a data store class, and reads as follows:
- (id)init
{
self = [super init];
if (self) {
// snip
NSURL *storeURL = [NSURL fileURLWithPath:[self itemArchivePath]];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] init];
NSError *error = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
[NSException raise:#"Couldn't open the SQL file" format:#"Reason: %#", [error localizedDescription]];
}
// snip
}
return self;
}
The exception is happening on the addPersistentStoreWithType: line, so we're not making it to the exception in the if block. Here's what I'm being told:
2013-07-01 14:46:04.647 (app name)[5859:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary setObject:forKey:]: attempt to insert nil value (key: NSStoreModelVersionHashes)'
When I set a breakpoint on the faulty line, Xcode doesn't let me go inside the function call, so I'm not sure where NSStoreModelVersionHashes is coming from or how to avoid it being set to nil. I can po storeURL and it seems to be the proper URL. Resetting the iOS Simulator, using a different name for the SQL file, or other solutions I've found for issues which seem to be tangentally related hasn't seemed to help anything.
The persistent store coordinator needs the managed object model. It is usually created with
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];
where managedObjectModel has previously been created with
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"<yourModelName>" withExtension:#"momd"];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
Have a look at any of the Core Data sample code in the Apple Developer Library for examples
how to setup the Core Data stack correctly.
I am trying to use CoreData in a SenTestCase. The problem is the NSManagedObjectModel which is not found. I have tried to initialize it with a URL by searching for the mom file in the application bundle but I could not find it. That is why I switched to:
NSManagedObjectModel *objectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
This does work properly but only in the main application. If I try to run it in the SenTestCase the returned objectModel does not have any entities:
(gdb) po objectModel
(<NSManagedObjectModel: 0xab72480>) isEditable 0, entities {
}, fetch request templates {
}
I have to admit that I do not know what kind of setup is needed to generate the mom file. I have added the .xcdatamodeld file to the list of compiled sources in the application's target and the test target.
The following code is working properly in the applications ViewController viewDidLoad method but is not working in a test case of a SenTestCase class:
- (void)testCoreData {
NSManagedObjectModel *objectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: objectModel];
[context setPersistentStoreCoordinator: coordinator];
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"TestDatabase"];
NSLog(#"Path: %#", url);
NSError *error;
NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:url
options:nil
error:&error];
if (newStore == nil) {
NSLog(#"Store Configuration Failure\n%#",
([error localizedDescription] != nil) ?
[error localizedDescription] : #"Unknown Error");
}
TaskSet *taskSet = [NSEntityDescription insertNewObjectForEntityForName:#"TaskSet"
inManagedObjectContext:context];
taskSet.taskSetId = #"1234";
NSLog(#"TaskSet: %#", taskSet);
NSError *saveError;
if ([context save:&saveError]) {
NSLog(#"Store Saved successfully");
} else {
NSLog(#"Store not saved! Error: %#", saveError);
}
}
Error:
Catchpoint 3 (exception thrown).Unknown.m:0: error: -[ControllerTest testCoreData] : +entityForName: could not locate an entity named 'TaskSet' in this model.
What is the difference between running in a SenTestCase and running in viewDidLoad ? What do I need to do to compile the core data model into a test case?
This question has been answered before: Unit Test can't find Core Data model file
The credit goes to Luther Baker for pointing out that SenTestCases are not using the main Bundle. If you want to write data to a file you have to create the bundle with an identifier as described in his answer.
I need to delete my persistent store (doing it object by object is not practical because I have over 100,000 objects). I've tried this:
- (IBAction)resetDatabase:(id)sender {
NSPersistentStore* store = [[__persistentStoreCoordinator persistentStores] lastObject];
NSError *error = nil;
NSURL *storeURL = store.URL;
// release context and model
[__managedObjectContext release];
[__managedObjectModel release];
__managedObjectModel = nil;
__managedObjectContext = nil;
[__persistentStoreCoordinator removePersistentStore:store error:nil];
[__persistentStoreCoordinator release];
__persistentStoreCoordinator = nil;
[[NSFileManager defaultManager] removeItemAtPath:storeURL.path error:&error];
if (error) {
NSLog(#"filemanager error %#", error);
}
// recreate the stack
__managedObjectContext = [self managedObjectContext];
}
But I get this error when I try to insert entities into the store afterwards:
This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.
Update:
I tried releasing the MOC and MOM before removing the persistent store but I still get the same error.
Here is how I do a "reset data" function in several apps:
- (void)reset {
// Release CoreData chain
[_managedObjectContext release];
_managedObjectContext = nil;
[_managedObjectModel release];
_managedObjectModel = nil;
[_persistentStoreCoordinator release];
_persistentStoreCoordinator = nil;
// Delete the sqlite file
NSError *error = nil;
if ([fileManager fileExistsAtPath:_storeURL.path])
[fileManager removeItemAtURL:_storeURL error:&error];
// handle error...
}
Basically I just release the CoreData chain, then delete the persistentStore file. That's what you are trying to do, without using removePersistentStore, which I do not care since I will just rebuild the persistentStore coordinator later. Then at next core data call the chain is rebuilt transparently using singleton-lazy-style constructors like :
- (NSManagedObjectModel *) managedObjectModel {
if (!_managedObjectModel)
_managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
return _managedObjectModel;
}
You can do it externally given that you only need to do this while developing your application. I have a terminal open in which I remove the store manually before re-running my app. All you need to know is where it is located. I log it to console everytime my app runs with the following code:
[[CoreDataSingleton sharedManager] managedObjectContext]; //be sure to create the store first!
//Find targeted mom file in the Resources directory
NSString *momPath = [[NSBundle mainBundle] pathForResource:#"Parking" ofType:#"mom"];
NSLog(#"momd path: %#",momPath);
Hope that helps!
You need to make sure that any managed object context attached to the persistent store have been released before you try to delete the store. Otherwise, the context will evoke that error.
I have been developing an iphone application using a domain model, and have put off the persistence aspect of the app until now. Core Data looks like a really good solution since I already have a well defined model but I am running into a snag with my existing unit tests.
Here is simple example of what I have now:
- (void)test_full_name_returns_correct_string {
Patient *patient = [[Patient alloc] init];
patient.firstName = #"charlie";
patient.lastName = #"chaplin";
STAssertTrue([[patient fullName] isEqualToString:#"charlie chaplin"], #"should have matched full name");
}
How can I make this work once my Patient object extends from NSManagedObject and uses #dynamic for the firstName and lastName properties?
Has anyone else run into this type of this with Core Data? Thanks.
You need to build a Core Data stack, either within each method or in -setUp and then tear it down. Using an NSInMemoryPersistentStore will keep things fast and in-memory for your unit tests. Add a #property (nonatomic,retain) NSManagedObjectContext *moc to your TestCase subclass. Then:
- (void)setUp {
NSManagedObjectModel *mom = [NSManagedObjectModel mergedModelFromBundles:[NSArray arrayWithObject:bundleContainingXCDataModel]];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
STAssertTrue([psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL] ? YES : NO, #"Should be able to add in-memory store");
self.moc = [[NSManagedObjectContext alloc] init];
self.moc.persistentStoreCoordinator = psc;
[mom release];
[psc release];
}
- (void)tearDown {
self.moc = nil;
}
Your test method then looks like:
- (void)test_full_name_returns_correct_string {
Patient *patient = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:self.moc];
patient.firstName = #"charlie";
patient.lastName = #"chaplin";
STAssertTrue([[patient fullName] isEqualToString:#"charlie chaplin"], #"should have matched full name");
}
assuming your entity is named Person. There was a memory leak in your version of the method, by the way; patient should be -release'd in the non-Core Data version (insertNewObjectForEntityForName:managedObjectContext: returns an autoreleased instance).
I used the answer above by Barry Wark, but I had to do some modifications to make it work with the current projects Xcode 5, iOS 7.
The property stayed the same:
#interface SIDataTest : XCTestCase
#property (nonatomic, retain) NSManagedObjectContext *moc;
#end
The setup had to actually had to change first of all to not release and secondly to provide a model URL.
- (void)setUp
{
[super setUp];
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"SimpleInvoice" withExtension:#"momd"];
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
XCTAssertTrue([psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL] ? YES : NO, #"Should be able to add in-memory store");
self.moc = [[NSManagedObjectContext alloc] init];
self.moc.persistentStoreCoordinator = psc;
}
Here is the example test case:
- (void)testCreateNew
{
Invoice *newInvoice = [NSEntityDescription insertNewObjectForEntityForName:#"Invoice" inManagedObjectContext:self.moc];
newInvoice.dueDate = [NSDate date];
NSString* title = [[NSString alloc] initWithFormat:#"Invoice %#", #112];
newInvoice.title = title;
// Save the context.
NSError *error = nil;
if (![self.moc save:&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.
XCTFail(#"Error saving in \"%s\" : %#, %#", __PRETTY_FUNCTION__, error, [error userInfo]);
}
XCTAssertFalse(self.moc.hasChanges,"All the changes should be saved");
}
I've here a CoreData app (no document-based), 1 entity and 1 tableview for edit/add/remove "instances" of the entity. Now I can manual add and save but I would like to
a) save automaticly changes
b) add automaticly some "instances" with the first start.
I think a) can be solved with NSNotifications. But which to use by entities?
Any ideas how to solve a) or b)?
Thanks for every answer. =)
Autosave can be a bit trickier than you'd first expect sometimes, since there may be times when your application data is in an invalid state (for instance, when the user is editing an entity) and either cannot be saved or it would not make sense to save. There's no easy setAutosaves:YES property unfortunately, so you'll have to implement it yourself. Using a notification to save after certain actions is one way to do it, you could also set up a timer to save periodically if it makes sense for your application.
To populate an empty data file, just check to see if the data store is empty at launch (applicationDidFinishLaunching and awakeFromNib are two possible places to put this), and if it is insert some entities as normal. The only tricky part is disabling undo management during the process. Here's an example from one of my applications:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
{
NSURL *fileURL = [NSURL fileURLWithPath:[self.applicationSupportFolder stringByAppendingPathComponent:WLDataFileName]];
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:fileURL options:nil error:NULL];
[context setPersistentStoreCoordinator:coordinator];
[request setEntity:[NSEntityDescription entityForName:#"Shelf" inManagedObjectContext:context]];
if ( [context countForFetchRequest:request error:NULL] == 0 )
[self _populateEmptyDataStore:context];
_managedObjectContext = [context retain];
[request release];
[coordinator release];
[context release];
// finish loading UI, etc...
}
- (void)_populateEmptyDataStore:(NSManagedObjectContext *)context;
{
[[context undoManager] disableUndoRegistration];
WLSmartShelfEntity *allItems = [NSEntityDescription insertNewObjectForEntityForName:#"SmartShelf" inManagedObjectContext:context];
WLSmartShelfEntity *trash = [NSEntityDescription insertNewObjectForEntityForName:#"SmartShelf" inManagedObjectContext:context];
allItems.name = NSLocalizedString( #"All Items", #"" );
allItems.predicate = [NSPredicate predicateWithFormat:#"isTrash = FALSE"];
allItems.sortOrder = [NSNumber numberWithInteger:0];
allItems.editable = [NSNumber numberWithBool:NO];
trash.name = NSLocalizedString( #"Trash", #"" );
trash.predicate = [NSPredicate predicateWithFormat:#"isTrash = TRUE"];
trash.sortOrder = [NSNumber numberWithInteger:2];
trash.editable = [NSNumber numberWithBool:NO];
[context processPendingChanges];
[[context undoManager] enableUndoRegistration];
DebugLog( #"Filled empty data store with initial values." );
}
Have a look at this thread on the Apple Mailing lists regarding autosave and Core Data.