NSManagedObject Entity Inheritance causes NSInternalInconsistencyException - objective-c

I have an NSManagedObject ElementA with several attributes that should exist in ElementB through a parent-child relationship. When setting ElementA as the Parent Entity to ElementB, the NSPersistentStoreCoordinator fails. The ManagedObjectModel is correctly being built, and the entities/classes work separate of each other. The only difference between the app failing and compiling is this parent-child relationship. None of the attributes from either entity overlap.
I don't have enough rep yet, so the images are at the following links: ElementA Model, ElementB Model.
As far as troubleshooting goes, I've tried all of the following:
With and without implementing custom classes.
Setting ElementA as abstract (however I need it to not be abstract)
Removing and then adding in the attributes one at a time (including emptying all attributes of both entities)
Resetting Xcode (clean), the simulator (reset all), and restarting my machine.
I've read up on Apple's Docs (Core Data Programming Guide: Managed Object Models) and everything seems to align with their guidelines for Entity Inheritance.
This is the line that fails:
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
What do I seem to be missing here? It's got to be obvious as this does not seem like it should be this hard. Any and all help is appreciated!
Edit for #Rog's Comment
The application fails as soon as the core data model is accessed for the first time at startup. The new images above show that I am trying to set the Parent Entity of ElementB using the Model Editor. The following is the error message I'm receiving:
uncaught exception 'NSInternalInconsistencyException', reason: 'Bad model. For entity 'ElementA' subentity 'ElementB (0x785d790)' is not registered in NSManagedModelModel. Model has a reference to ElementB (0x785e320)'

Not the full code... but this is how I achieved what #Scott BonAmi is talking about when removing the temporary entities. As I'm still using modelByMergingModels:, it figures out the sub entities itself.
NSMutableArray *finalModels = [NSMutableArray arrayWithCapacity:0];
NSMutableArray *updatedEntities = [NSMutableArray arrayWithCapacity:0];
for (NSManagedObjectModel *immutableModel in allModels) {
NSManagedObjectModel *model = [immutableModel mutableCopy];
for (NSEntityDescription *entity in [model entities]) {
if ([[[entity userInfo] objectForKey:#"TempPlaceholder"] boolValue]) {
// Ignore placeholder.
DULog(#"Ignoring: %#", entity.name);
} else {
[updatedEntities addObject:entity];
}
}
[model setEntities:updatedEntities];
[updatedEntities removeAllObjects];
[finalModels addObject:model];
}
NSManagedObjectModel *model = [NSManagedObjectModel modelByMergingModels:finalModels];

Ended up being a logic error with code I used from another SO answer creating the MOM dynamically.
When adding entities to the array during the looping sequence, ElementB (0x785d790) is added as a subentity of ElementA, and then later in the loop 'ElementB (0x785e320)' is added, thus causing different memory locations and throwing an NSInternalInconsistencyException.

Related

How to use Core Data models without saving them?

I'm writing an application and I am using MagicalRecord as a framework for interacting with Core Data. The application fetches an array of posters from a server and then displays them. Posters can also be created on the app and then uploaded to the server if the user requires it.
So posters created by the user are stored in the local db using Core Data, while posters fetched from the server should only be displayed in the app but not saved locally. How can I use the same Poster class (which now is a subclass of NSManagedObject) to handle both these cases?
Here is my class:
#interface Poster : NSObject
#property (nonatomic, retain) NSNumber * posterID;
#property (nonatomic, retain) NSString * artists;
#end
When I fetch the posters array from the server I allocate a new poster and then assign attributes:
Poster *poster = [[Poster alloc] init];
if ([dict objectForKey:#"id"]) poster.posterID = [dict objectForKey:#"id"];
if ([dict objectForKey:#"artists"]) poster.artists = [dict objectForKey:#"artists"];
But when reaching the linked poster.posterID = [dict etc etc the application crashes with this error
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Poster setPosterID:]: unrecognized selector sent to instance 0xaa8b160'
If I create the new object with Poster *poster = [Poster createEntity]; instead of Poster *poster = [[Poster alloc] init];, the app doesn't crash, but when I save the context I find all the posters fetched from the server are saved locally.
How can I solve this?
You cannot just alloc/init a managed object, because a managed object must be associated with a managed object context. poster.posterID = ... crashes because the dynamically created accessor methods do not work without a managed object context. (Correction: As #noa correctly said, you can create objects without a managed object context, as long as you use the designated initializers. But those objects would not be "visible" to any fetch request.)
To create managed objects that should not be saved to disk you can work with two persistent stores: one SQLite store and a separate in-memory store.
I cannot tell you how to do that with MagicalRecord, but with "plain Core Data" it would work like this:
After creating the managed object context and the persistent core coordinator, you assign two persistent stores to the store coordinator:
NSPersistentStore *sqliteStore, *memStore;
sqliteStore = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
if (sqliteStore == nil) {
// ...
}
memStore = [coordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error];
if (memStore == nil) {
// ...
}
Later, when you insert new objects to the context, you associate the new object either with the SQLite store or the in-memory store:
Poster *poster = [NSEntityDescription insertNewObjectForEntityForName:#"Poster" inManagedObjectContext:context];
[context assignObject:poster toPersistentStore:memStore];
// or: [context assignObject:poster toPersistentStore:sqliteStore];
poster.posterID = ...;
poster.artists = ...;
Only the objects assigned to the SQLite store are saved to disk. Objects assigned to the in-memory store will be gone if you restart the application. I think that objects that are not assigned explicitly to a store are automatically assigned to the first store, which would be the SQLite store in this case.
I haven't worked with MagicalRecord yet, but I see that there are methods MR_addInMemoryStore and MR_addSqliteStoreNamed, which would be the appropriate methods for this configuration.
You could also try using the designated initializer -initWithEntity:insertIntoManagedObjectContext: with nil for the second parameter. (In my experience, some aspects of managed objects work fine without a context; others do not.)
There's a bit of further explanation in this answer.

Emptying a Core Data NSSet (multiple relationships)

If I need to programmatically empty a NSSet automatically created by Core Data (multiple relationships), what should I do ? Something like this ?
[self willChangeValueForKey:#"MyRelationship"];
[[self MyRelationship] release];
[self MyRelationship] = [NSSet alloc] init];
[self didChangeValueForKey:#"MyRelationship"];
Not sure it is correct at all...
thanks
[[self mutableSetValueForKey:#"MyRelationship"] removeAllObjects];
For some reason, I can never get the "cascade" delete rule to work, so when I want the objects deleted as well I have to iterate over the set and call [self.managedObjectContext deleteObject:obj] or else I'll get validation errors, if the relationship is defined as required.
Patrick,
Relations are, unsurprisingly, special in Core Data. They provide some specialized methods to remove objects from those relations. Rather than trying to override the accessors, you should use those methods. As in this snippet:
[self removeMyRelationship: self.myRelationship];
I also think your should remove your overridden accessor methods.
I have no insight into your deletion problem. I recommend that you just iterate over the group and delete the objects. I think it is important that your enumerator be a copy of your relationship. As in the following ARC code:
for (Relation *r in [self.myRelationship copy]) {
[moc deleteObject: r];
}
Andrew

NSArrayController and the exception "CoreData could not fulfill a fault"

I have a list of items, instances of a Item class saved in a Core Data model.
This items are showed in a NSTableView using an NSArrayController and Cocoa Bindings. It works very well.
However, when I remove some items using these instructions:
// Removes selected items
for (Item *item in self.itemsArrayController.selectedObjects) {
[self.managedObjectContext deleteObject:item];
}
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
[[NSApplication sharedApplication] presentError:error];
}
after some times, I obtain the exception CoreData could not fulfill a fault.
I read all the documentation that I found (including the Troubleshooting Core Data), but I did not find anything useful.
I'm using the new ARC (Automatic Reference Counting), so I'm pretty sure I'm not trying to access, after the save on the managed object context, the managed object which was deleted.
UPDATE: My app is single thread, so I'm not trying to access the managedObjectContext from multiple threads.
Are you accessing the same managedObjectContext on multiple threads? This feels like a race condition where you delete an object that the MOC expects to be around. A given NSManagedObjectContext can only be accessed from a single thread.
You are enumerating the selected items of the array controller, and deleting the objects while enumerating. Try:
NSArray *selectedObjects = [[self.itemsArrayController selectedObjects] copy];
for (Item *item in selectedObjects) {
[self.managedObjectContext deleteObject:item];
}
[selectedObjects release];

can't find managed object model?

I created a entity called photo inmy .xcdatamodel.
but when I tried to add it into my context:
NSManagedObjectContext *context = [self managedObjectContext];
Photo *p = [NSEntityDescription insertNewObjectForEntityForName:#"Photo" inManagedObjectContext:context];
it had run-time error:
+entityForName: could not locate an NSManagedObjectModel for entity name
'Photo'
it's really weird, I included the Photo.h, generated by xcode coredata.
does anyone have an idea why it goes wrong?
I can't find what's wrong at all.. > <
Thanks!
Make sure your call to [self managedObjectContext] is returning a valid context (and not nil). Also make sure you're using the proper case (you said your entity was called "photo" but you're trying to insert a new object for an entity named "Photo").

Customized initializers and read only properties in Core Data

Before working with Objective-C and Core Data, I had occasions to create classes that needed to be initialized with certain parameters that, after initialization, could not be modified (though they could be read).
With Core Data, I believe I can create a customized init on my NSManagedObject derived class as long as it includes a way to insert the object into a context like such:
-(Cell*) initWithOwner:(CellList*)ownerCellList andLocation:(int)initLocation
{
if (self = (Cell*) [NSEntityDescription insertNewObjectForEntityForName:#"Cell"
inManagedObjectContext:[ownerCellList managedObjectContext]])
{
self.location = [NSNumber numberWithInt:initLocation];
self.owner = ownerCellList;
[ownerCellList addCellListObject:self];
}
return self;
}
Normally, I'd have a location variable and the location property would be read-only (so once set at initialization, it could not be changed). Is there a way to get this sort of pattern with Core Data? Is there a better way I'm not thinking of?
Thanks!
You are correct. As long as your initializer calls the NSManagedObject's designated initializer, your approach is fine. You can also override the -[NSManagedObject awakeFromInsert] to perform some action after insertion (creation) or -[NSManagedObject awakeFromFetch] to perform an action (e.g. populating a cache) each time the object is faulted back into a managed object context.
Like the rest of Objective-C, there is no way to make a property truly readonly. Malicious code will likely be able to modify your property. However, in your custom class, you can declare a #property(readonly) for e.g. location. This will at least cause a warning if you try to modify the property and will signal your intent to client code.
For anyone who stumbles here, reads the comments, and wonders at the final answer, it should be something like this. Continuing with the example above, it would be:
-(Cell*) initWithOwner:(CellList*)ownerCellList andLocation:(int)initLocation
{
NSManagedObjectContext *context = [ownerCellList managedObjectContext];
NSManagedObjectModel *managedObjectModel =
[[context persistentStoreCoordinator] managedObjectModel];
NSEntityDescription *entity =
[[managedObjectModel entitiesByName] objectForKey:#"Cell"];
self = [self initWithEntity:entity inManagedObjectContext:context];
if (self)
{
self.location = [NSNumber numberWithInt:initLocation];
self.owner = ownerCellList;
[ownerCellList addCellListObject:self];
}
return self;
}
NSEntityDescription's insertNewObjectForEntityForName:inManagedObjectContext: documentation says this is roughly how it converts from a given entityName (#"Cell") and a context (from ownerCellList) to an NSManagedObject instance.