i have a problem in my iOS App, i receive this error:
“NSObjectInaccessibleException - CoreData could not fulfill a fault”
When i update the database, i do it from another thread and i have in that thread this method:
#interface UpdateDatabase : NSOperation
#property (nonatomic,copy) NSString *name;
#end
#implementation UpdateDatabase
- (void)mergeChanges:(NSNotification *)notification
{
AppDelegate *appController = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appController managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}
- (void)main {
AppDelegate *appController = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = [[NSManagedObjectContext alloc] init];
[self.managedObjectContext setUndoManager:nil];
[self.managedObjectContext setPersistentStoreCoordinator: [appController persistentStoreCoordinator]];
// Register context with the notification center
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:self.managedObjectContext];
[self checkForUpdate:self.name];
}
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
// Set up the fetched results controller.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Serial" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sectionNumber" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"sectionNumber" cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&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.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
- (void)checkForUpdate:(NSString *)name
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Serial" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
//Controllo prima che la serie di cui voglio fare l'aggiornamento non sia in download, e non stia facendo già un aggiornamneto (caso mai per qualche strana ragione non ne siano partiti 2), prendo solo le altre.
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"%K == %#", #"serialName",self.name]];
NSError *error;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedObjects) {
//i update the object
}
if (![self.managedObjectContext 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. If it is not possible to recover from the error, display an alert
// panel that instructs the user to quit the application by pressing the Home button.
//
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
#end
and i launch a new nsoperation for every element in the core data database, then in the view where i have the UITableView, i have this:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil) {
return __fetchedResultsController;
}
// Set up the fetched results controller.
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Serial" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sectionNumber" ascending:NO];
NSSortDescriptor *numberDayDescriptor = [[NSSortDescriptor alloc] initWithKey:#"numberOfDays" ascending:YES];
NSSortDescriptor *serialNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"serialName" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor,numberDayDescriptor,serialNameDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"sectionNumber" cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&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.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
Sometime the app crashes and some time don't crash, how i can handle this error? i have read some question about this problem on SO, and someone talk about remove the fetchbatchsize and the cache, what you think? where i wrong?
EDIT:
I have edited the code.
Related
There is some serious issue with NSFetchedResultsController which has a predicate. the issue occurs after a value is updated for a property of a contact. so that's how its configured:
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Contacts" inManagedObjectContext:_addressbookMainObjectContext
NSSortDescriptor *sortNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastnameFirstLetter" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSSortDescriptor *sortNameDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"firstName" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortNameDescriptor, sortNameDescriptor1, nil];
NSFetchRequest *fetchRequest = [NSFetchRequest new];
[fetchRequest setEntity:entityDescription];
[fetchRequest setSortDescriptors:sortDescriptors];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"contactType == 3"];;
NSFetchedResultsController *fetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_addressbookMainObjectContext
sectionNameKeyPath:#"sectiononIdentifier"
cacheName:nil];
_fetchResultController = fetchedResultsController;
_fetchResultController.delegate = self;
the predicate is set to fetch filtered rows.
and this is how the parent child context is configured:
_addressbookMainObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_addressbookMainObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
_addressbookMainObjectContext setParentContext:_writerContext];
_writerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_writerContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[_writerContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
for updating a row another private context is used to do the job:
NSManagedObjectContext *ctx = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[ctx setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[ctx setParentContext:[PalDataCache sharedInstance].addressbookMainObjectContext];
[ctx performBlockAndWait:^{
NSError *errorAllCons = nil;
NSArray *allCons = [context executeFetchRequest:allContacts error:&errorAllCons];
Contacts * contact = [allCons objectAtIndex:0];
contact.pictureUpdatedForRedownload = [NSNumber numberWithBool:YES];
}];
so after saving the changes in this temporary context. the NSFetchedResultsControllerDelegate is getting triggered:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case NSFetchedResultsChangeDelete:
{
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
case NSFetchedResultsChangeUpdate:
[self configureCell:(contactCell*)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
}
and this triggers NSFetchedResultsChangeDelete that causes the row to get delete from the table but its not deleted from database. after refetching everything is back there. and that's a very bad user experience. the predicate is set according to the user defined filter. the issue doesn't happen if there is no predicate in fetch request. what am I doing wrong? is there a work around for this behaviour?
edit:
this issue still occur when i try to update a record on the main context for contacts. so it's then main context and write context combination.
I faced the same problem. This is how I fixed:
My predicate was using something like ("index == %#")
I was filtering an integer value, but I was passing #"1".
Changing it to %i and passing integer, worked.
So basically I have a table view of folder objects and I want to be able to remove/ delete folders. So far if I try to delete, the folders are removed, but when I re run the application they are all back (so the delete is not saved). Any advice?
here is my delete method for the UITableView:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[self.folders removeObjectAtIndex:indexPath.row];
NSMutableArray *newSavedFolders = [[NSMutableArray alloc] init];
for (Folder *folder in self.folders){
[newSavedFolders addObject:[self folderWithName:folder.name]];
}
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
and the folderWithName method is from here:
- (Folder *)folderWithName:(NSString *)name {
id delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [delegate managedObjectContext];
Folder *folder = [NSEntityDescription insertNewObjectForEntityForName:#"Folder" inManagedObjectContext:context];
folder.name = name;
folder.date = [NSDate date];
NSError *error;
if (![context save:&error]) {
//we have an error
}
return folder;
}
The reason it deletes is because of this line:
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
That removes the row from view, but doesn't remove the data.
If your using CoreData then you simply do the following with a NSFetchedResultsController:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
[self.tableView reloadData];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Can't Delete! %# %#", error, [error localizedDescription]);
return;
}
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
Add call your items with the NSFetchedResultsController this way:
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Folder" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number for displaying in UITableView.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&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.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return fetchedResultsController;
}
I set up CoreData without NSFetchedResultsController and had everything saving fine. After switching to NSFetchedResultsController, I am getting a strange error when trying to save the image.
Here is the code I am using to save the image:
- (void)saveImage {
NSManagedObjectContext *context = [self managedObjectContext];
TimeTravelFeed *timeTravelFeed = [NSEntityDescription insertNewObjectForEntityForName:#"TimeTravelFeed" inManagedObjectContext:context];
NSData *imageData = UIImageJPEGRepresentation(self.thumbImage, 0.8f);
[timeTravelFeed setValue:imageData forKey:#"imageData"];
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
//[tableView reloadData];
}
And here is the error message:
-[_PFExternalReferenceData compare:]: unrecognized selector sent to instance 0x1669fb40
2013-12-08 10:09:49.442 Time Travel[830:60b] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.
-[_PFExternalReferenceData compare:]: unrecognized selector sent to instance 0x1669fb40 with userInfo (null)
2013-12-08 10:09:49.443 Time Travel[830:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_PFExternalReferenceData compare:]: unrecognized selector sent to instance 0x1669fb40'
Here is the code for NSFetchedResultsController:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *detailsViewController = [[DetailViewController alloc] init];
[self.navigationController pushViewController:detailsViewController animated:YES];
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"TimeTravelFeed" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"imageData" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
The problem seems to be here:
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"imageData" ascending:NO];
You cannot sort on a "Binary Data" attribute. A fetched results controller
needs a sort descriptor, so you should use a different attribute, e.g. a string, number or date. For example
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"creationDate" ascending:NO];
where "creationDate" is an attribute (type "Date") and set when the object is created:
[timeTravelFeed setValue:imageData forKey:#"imageData"];
[timeTravelFeed setValue:[NSDate date] forKey:#"creationDate"];
Go to your DBfile, select entity named "TimeTravelFeed" and select key "imageData". Set properties.
I can't figure this out, but I seem to have a null indexPath when I delete an object from the NSFetchedResultsController.
When I delete my object, I do this:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the object
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
[self saveContext];
}
}
Setting up NSFetchedResultsController:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setFetchBatchSize:20];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Route" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *nameSort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *sortDescriptors = #[nameSort];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Routes"];
self.fetchedResultsController = aFetchedResultsController;
self.fetchedResultsController.delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error with NSFetchedResultsController: %#", [error description]);
abort();
}
return _fetchedResultsController;
}
This is where the failure occurs:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch (type) {
// Data was inserted - insert the data into the table view
case NSFetchedResultsChangeInsert: {
[self.savedRoutesTableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
// Data was deleted - delete the data from the table view
case NSFetchedResultsChangeDelete: {
[self.savedRoutesTableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
case NSFetchedResultsChangeUpdate: {
SavedRoutesTableViewCell *cell = (SavedRoutesTableViewCell *)[self.savedRoutesTableView cellForRowAtIndexPath:indexPath];
[cell configureCellWithRoute:[controller objectAtIndexPath:newIndexPath]];
break;
}
case NSFetchedResultsChangeMove: {
[self.savedRoutesTableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.savedRoutesTableView insertRowsAtIndexPaths:#[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
}
default:
break;
}
}
In the didChangeObject method, both my indexPath and newIndexPath are nil. I can NSLog my object and I do see the entity. It crashes in the [self.savedRoutesTableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; method with the exception:
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. *** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0] with userInfo (null)
When I save this object, I save it like this:
self.route.name = routeName;
NSManagedObjectContext *tempContext = [self.route managedObjectContext];
[tempContext performBlock:^{
NSError *error = nil;
if (![tempContext save:&error]) {
NSLog(#"an error occurred: %#", [error localizedDescription]);
}
[self.managedObjectContext performBlock:^{
NSError *error = nil;
if (![_managedObjectContext save:&error]) {
NSLog(#"error in main context: %#", [error localizedDescription]);
}
}];
}];
I'm not really sure where else to debug this since the NSFetchedResultsController just isn't returning me the indexPath for the deleted object. Any thoughts? Thanks in advance.
Edit:
Well I found the culprit causing the error, but I'm not sure why it does. Basically I have a ViewController that receives either a Route entity from the main MOC if it's Editing the route, or it inserts a new one if you are creating a new route. So in that viewController, if I'm editing a route, because I am trying to use two MOCs, one temp, and one main for its parent so I can easily throw away stuff if the user decides to cancel and not create a new route, I needed to transfer over that route to the other context to make other code I have work. So that "transfer" looks like:
NSManagedObjectContext *moc = _route.managedObjectContext;
NSManagedObjectID *routeId = [_route objectID];
self.tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.tempContext.parentContext = moc;
NSManagedObject *localRoute = [self.tempContext objectWithID:routeId];
self.route = localRoute;
With this code, my adding on locations to an existing route works now that the locations are in the same contexts, but somehow it messes up deleting an existing route from the main MOC. Not sure why and what the best solution is.
Had the same problem and solved it by ensuring that NSFetchedResultsController always uses the same NSManagedContext. For example if u fetch object from a database with a fetch controller and later you want to delete that object, make sure that the fetch controller uses the same managed context it was using during fetching.
NSFetchedResultsController is optimised for working with UITableView and UITableView is user interface component, and user interface should always be handled by main thread, so there is no need to create new NSManagedContext every time you go into fetch... So implementing this code should fix this problem:
#synthesize fetchedResultsController = __fetchedResultsController;
#synthesize managedObjectContext = __managedObjectContext;
- (NSManagedObjectContext *)managedObjectContext
{
if (__managedObjectContext != nil)
{
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = ap.persistentStoreCoordinator;
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return __managedObjectContext;
}
ap is a pointer to AppDelegate:
ap = [[UIApplication sharedApplication] delegate];
What this code does is creates one instance of MOC and later reuses it. Warning: this is not threadsafe, and it doesn have to be (cause you should use it with main thread only), so if you missuse it it will not work...
Use that:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
//create __fetchedResultsController
// do some fetching
return __fetchedResultsController;
}
So when you want to populate table:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [__fetchedResultsController.fetchedObjects count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSManagedObject *managedObject = [__fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [managedObject valueForKey:#"smth."];
return cell;
}
If your dataset changes just:
__fetchedResultsController = nil;
[tableView reloadData]
And everything including NSFetchedResultsControllers delegat methods will work fine, no nil indexPaths and so on...
Hope I helped someone...
NSManagedObjectContext *moc = _route.managedObjectContext;
NSManagedObjectID *routeId = [_route objectID];
self.tempContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.tempContext.parentContext = moc;
NSManagedObject *localRoute = [self.tempContext objectWithID:routeId];
self.route = localRoute;
Had to get the context associated with the route.
Sorry for my terrible english.
I have big problem in my iOS app. Application has big database which is managed by Core Data. And I have many TableView Controllers for displaying this data. Any change in database should be shown in tableview. It can be reached by implementing NSFetchedResultsController delegate protocol. All realization are very simple like in books. If application starts in simulator first time and I add new entries in some tables next delegate methods are successfully fired:
– controllerWillChangeContent:
– controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
– controllerDidChangeContent:
After stop debugging and start application again none of listed methods are fired.
They calls only after [managedObjectContext save] operation will perform.
Have you any ideas why this happens?
Source code:
//IssueProfileViewController.h class implements NSFetchedResultController delegate methods
- (NSFetchedResultsController*)fetchedResultsController{
if (_fetchedResultsController == nil) {
NSManagedObjectContext *managedObjectContext = self.issue.managedObjectContext;
NSFetchRequest *aFetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"IssueHistoryItem" inManagedObjectContext:managedObjectContext];
[aFetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"created" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
//NSPredicate *predicate = [[NSPredicate predicateWithFormat:#"issue == %# && isComment == NO", self.issue] retain];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"issue == %# && isComment == NO", self.issue];
[aFetchRequest setSortDescriptors:sortDescriptors];
[aFetchRequest setPredicate:predicate];
NSFetchedResultsController *aFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:aFetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[aFetchRequest release];
//[predicate release];
[sortDescriptors release];
[sortDescriptor release];
_fetchedResultsController = aFetchedResultsController;
_fetchedResultsController.delegate = self;
}
return _fetchedResultsController;
}
-(void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
-(void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
NSArray *paths;
// NSIndexSet *section = [NSIndexSet indexSetWithIndex:[newIndexPath section]];
NSIndexPath *cellContentIndexPath;
switch (type) {
case NSFetchedResultsChangeInsert:
// paths = [NSArray arrayWithObject:newIndexPath];
if (![anObject isKindOfClass:[ChangeIssueDimensionValueHistoryItem class]]) {
cellContentIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:newIndexPath.section];
paths = [NSArray arrayWithObject:cellContentIndexPath];
[self.tableView insertRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationFade];
[self sendMessageAboutObjectsCountChanged];
}
break;
case NSFetchedResultsChangeDelete:
paths = [NSArray arrayWithObject:indexPath];
[self.tableView deleteRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationFade];
[self sendMessageAboutObjectsCountChanged];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[self.tableView cellForRowAtIndexPath:indexPath]
withIssueHistoryItem:[self.fetchedResultsController objectAtIndexPath:indexPath]];
break;
default:
break;
}
}
That seems to be the proper way that the NSFetchedResultsController performs--responding to changes at the model layer (i.e. [managedObjectContext save]).
In the documentation: https://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html under 'Responding to Changes' it states that the controller will not show changes until the managed object's context has received a processPendingChanges message. That message can be triggered manually, also.