NSArrayController and the exception "CoreData could not fulfill a fault" - objective-c

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];

Related

Adding managedObjectContext objects with an NSArrayController

I have this application that is using core data and an NSArrayController to manage some objects in a table. I have the code below to pick up some objects on a directory. My questions is about the section below labeled "Handle Files". I create a new Video object using the url, I copy the metadata attributes using a custom function I wrote. The object is now inserted in the managedObjectContext. My question is, since I have my NSArrayController bound to my managedObjectContext, why do I have to still do [self addObject:newVideo] to have the object shown on my table? Is there a way to force the array controller to pull the object from the managedObjectContext without having to manually add it? It will be a hassle having to be updating both things every time I add or remove an object.
for (NSURL *url in _dirEnumerator) {
NSNumber *_isDirectory = nil;
[url getResourceValue:&_isDirectory forKey:NSURLIsDirectoryKey error:NULL];
if (![_isDirectory boolValue]) {
if (([_mediaTypes containsObject:[[url pathExtension]uppercaseString]])) {
// Handle the files
Video *newVideo = [NSEntityDescription insertNewObjectForEntityForName:#"Video" inManagedObjectContext:_managedObjectContext];
[newVideo copyAttributesFrom:url];
[self addObject:newVideo];
NSLog(#"Inserting video: %#",[newVideo valueForKey:#"name"]);
}
}
}
Well, I had my bindings all wrong an the array controller was not feeding my table correctly. You cannot sneak objects behind the array controller, if you implement the array controller you must let him do his job and that includes adding and removing objects. He will take care of letting the tableview know when things have changed.

Is mutableCopy call returns an array with a copy of the managed object context?

In a tutorial i'm learning CoreData from the preform something like this to fetch the collection of notes in a notes app:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Note"];
self.notes = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
So first of all, notes is an NSMutableArray, so tell me if I understand it right:
they creating an NSManagedObjectContext object to hold the context.
they create a request to get the "Note" entity from the database file.
they use the managedObjectContext to call executeFetchRequest with the requested request (which is fetchRequest). Now here is the part I dont completely understand (probably some of the previous ones as well, please correct me if I didn't):
The type of object i'm getting from this call [managedObjectContext executeFetchRequest:fetchRequest error:nil]; is an NSSet? and by calling mutableCopy i'm returning an array?
Thanks
[managedObjectContext executeFetchRequest:fetchRequest error:nil]
returns an (immutable) NSArray, and mutableCopy creates a - well - mutable copy
of that array. It does not copy the managed objects in the array or the context.
It just allows you to modify self.notes, e.g. to add, delete or rearrange the objects
in the mutable array.
Remark: If you display objects from a Core Data fetch request in a table view
then you should have a look at NSFetchedResultsController. It might look a bit more
complicated at the beginning, but allows (for example) automatic updates of the
table view if objects are inserted, deleted or modified.

NSFecthedResultsController and NSPredicate update issue [duplicate]

I am presenting table view contents using NSFetchedResultsController which has a predicate:
[NSPredicate predicateWithFormat:#"visible == %#", [NSNumber numberWithBool:YES]]
On background thread using separate NSManagedObjectContext I update few of the entities and change theirs visible value from NO to YES. Save, merge changes in main thread's NSManagedObjectContext. But NSFetchedResultsController's fetchedObjects doesn't change. Also controller doesn't call -controller:didChangeObject:... on delegate. If entities are updated on main thread in identical manner (my test app calls the same method), everything works as expected.
Also Notification's NSUpdatedObjectsKey contains those objects.
Currently the only solutions I've found is to call for each of NSUpdatedObjectsKey entities:
NSManagedObjectContext *context = ... // main thread context
[context existingObjectWithID:[object objectID] error:nil]
This issue is only with updated objects which previously didn't match the predicate.
Am I missing something obvious?
Turns out main NSManagedObjectContext didn' t event fire NSManagedObjectContextObjectsDidChangeNotification for updated objects because it is not done for faulted objects.
Generic fix (or keep a track of object IDs that needs this treatment):
NSManagedObjectContext *context = [self managedObjectContext];
for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) {
[[context objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[context mergeChangesFromContextDidSaveNotification:notification];
From NSManagedObject Class Reference:
You can invoke this method with the
key value of nil to ensure that a
fault has been fired, as illustrated
by the following example.
You have to call processPendingChanges on your Background-NSManagedObjectContext after you merged the changes from an other NSManagedObjectContext.
See CoreData Programming Guide:
Note that the change notification is sent in NSManagedObjectContext’s
processPendingChanges method. The main thread is tied into the event
cycle for the application so that processPendingChanges is invoked
automatically after every user event on contexts owned by the main
thread. This is not the case for background threads—when the method is
invoked depends on both the platform and the release version, so you
should not rely on particular timing. If the secondary context is not
on the main thread, you should call processPendingChanges yourself at
appropriate junctures.

CoreData import not caught by NSFetchedResultsController [duplicate]

I am presenting table view contents using NSFetchedResultsController which has a predicate:
[NSPredicate predicateWithFormat:#"visible == %#", [NSNumber numberWithBool:YES]]
On background thread using separate NSManagedObjectContext I update few of the entities and change theirs visible value from NO to YES. Save, merge changes in main thread's NSManagedObjectContext. But NSFetchedResultsController's fetchedObjects doesn't change. Also controller doesn't call -controller:didChangeObject:... on delegate. If entities are updated on main thread in identical manner (my test app calls the same method), everything works as expected.
Also Notification's NSUpdatedObjectsKey contains those objects.
Currently the only solutions I've found is to call for each of NSUpdatedObjectsKey entities:
NSManagedObjectContext *context = ... // main thread context
[context existingObjectWithID:[object objectID] error:nil]
This issue is only with updated objects which previously didn't match the predicate.
Am I missing something obvious?
Turns out main NSManagedObjectContext didn' t event fire NSManagedObjectContextObjectsDidChangeNotification for updated objects because it is not done for faulted objects.
Generic fix (or keep a track of object IDs that needs this treatment):
NSManagedObjectContext *context = [self managedObjectContext];
for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) {
[[context objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[context mergeChangesFromContextDidSaveNotification:notification];
From NSManagedObject Class Reference:
You can invoke this method with the
key value of nil to ensure that a
fault has been fired, as illustrated
by the following example.
You have to call processPendingChanges on your Background-NSManagedObjectContext after you merged the changes from an other NSManagedObjectContext.
See CoreData Programming Guide:
Note that the change notification is sent in NSManagedObjectContext’s
processPendingChanges method. The main thread is tied into the event
cycle for the application so that processPendingChanges is invoked
automatically after every user event on contexts owned by the main
thread. This is not the case for background threads—when the method is
invoked depends on both the platform and the release version, so you
should not rely on particular timing. If the secondary context is not
on the main thread, you should call processPendingChanges yourself at
appropriate junctures.

What's the way to communicate a set of Core Data objects stored in the background to the main thread?

Part of my iOS project polls a server for sets of objects, then converts and saves them to Core Data, to then update the UI with the results. The server tasks happens in a collection of NSOperation classes I call 'services' that operate in the background. If NSManagedObject and its ~Context were thread safe, I would have had the services call delegate methods on the main thread like this one:
- (void)service:(NSOperation *)service retrievedObjects:(NSArray *)objects;
Of course you can't pass around NSManagedObjects like this, so this delegate method is doomed. As far as I can see there are two solutions to get to the objects from the main thread. But I like neither of them, so I was hoping the great StackOverflow community could help me come up with a third.
I could perform an NSFetchRequest on the main thread to pull in the newly added or modified objects. The problem is that the Core Data store contains many more of these objects, so I have to add quite some verbosity to communicate the right set of objects. One way would be to add a property to the object like batchID, which I could then pass back to the delegate so it would know what to fetch. But adding data to the store to fix my concurrency limitations feels wrong.
I could also collect the newly added objects' objectID properties, put them in a list and send that list to the delegate method. The unfortunate thing though is that I have to populate the list after I save the context, which means I have to loop over the objects twice in the background before I have the correct list (first time is when parsing the server response). Then I still only have a list of objectIDs, which I have to individually reel in with existingObjectWithID:error: from the NSManagedObjectContext on the main thread. This just seems so cumbersome.
What piece of information am I missing? What's the third solution to bring a set of NSManagedObjects from a background thread to the main thread, without losing thread confinement?
epologee,
While you obviously have a solution you are happy with, let me suggest that you lose some valuable information, whether items are updated, deleted or inserted, with your mechanism. In my code, I just migrate the userInfo dictionary to the new MOC. Here is a general purpose routine to do so:
// Migrate a userInfo dictionary as defined by NSManagedObjectContextDidSaveNotification
// to the receiver context.
- (NSDictionary *) migrateUserInfo: (NSDictionary *) userInfo {
NSMutableDictionary *ui = [NSMutableDictionary dictionaryWithCapacity: userInfo.count];
NSSet * sourceSet = nil;
NSMutableSet *migratedSet = nil;
for (NSString *key in [userInfo allKeys]) {
sourceSet = [userInfo valueForKey: key];
migratedSet = [NSMutableSet setWithCapacity: sourceSet.count];
for (NSManagedObject *mo in sourceSet) {
[migratedSet addObject: [self.moc objectWithID: mo.objectID]];
}
[ui setValue: migratedSet forKey: key];
}
return ui;
} // -migrateUserInfo:
The above routine assumes it is a method of a class which has an #property NSManagedObjectContext *moc.
I hope you find the above useful.
Andrew
There's a section of the Core Data Programming Guide that addresses Concurrency with Core Data. In a nutshell, each thread should have its own managed object context and then use notifications to synchronize the contexts.
After a little experimentation, I decided to go for a slight alteration to my proposed method number 2. While performing background changes on the context, keep a score of the objects you want to delegate back to the main thread, say in an NSMutableArray *objectsOfInterest. We eventually want to get to the objectID keys of all the objects in this array, but because the objectID value changes when you save a context, we first have to perform that [context save:&error]. Right after the save, use the arrayFromObjectsAtKey: method from the NSArray category below to generate a list of objectID instances, like so:
NSArray *objectIDs = [objectsOfInterest arrayFromObjectsAtKey:#"objectID"];
That array you can pass back safely to the main thread via the delegate (do make sure your main thread context is updated with mergeChangesFromContextDidSaveNotification by listening to the NSManagedObjectContextDidSaveNotification). When you're ready to reel in the objects of the background operation, use the existingObjectsWithIDs:error: method from the category below to turn the array of objectID's back into a list of working NSManagedObjects.
Any suggestions to improve the conciseness or performance of these methods is appreciated.
#implementation NSArray (Concurrency)
- (NSArray *)arrayFromObjectsAtKey:(NSString *)key {
NSMutableArray *objectsAtKey = [NSMutableArray array];
for (id value in self) {
[objectsAtKey addObject:[value valueForKey:key]];
}
return objectsAtKey;
}
#end
#implementation NSManagedObjectContext (Concurrency)
- (NSArray *)existingObjectsWithIDs:(NSArray *)objectIDs error:(NSError **)error {
NSMutableArray *entities = [NSMutableArray array];
#try {
for (NSManagedObjectID *objectID in objectIDs) {
// existingObjectWithID might return nil if it can't find the objectID, but if you're not prepared for this,
// don't use this method but write your own.
[entities addObject:[self existingObjectWithID:objectID error:error]];
}
}
#catch (NSException *exception) {
return nil;
}
return entities;
}
#end