NSFetchedResultsController not changing sort order on entity update; does update and not move

Viewed 420

My iOS app has a view controller that displays a list of inbox items. These inbox items are an entity in the core data database and the list is managed in the view controller by an NSFetchedResultsController using a main thread managed context (NSMainQueueConcurrencyType).

Except the NSFetchedResultsController, all access to the entities is confined to a code running on a dedicated GCD dispatch queue with a private context. So setting of attribute values in the entity, as well as reading those entity attribute values, happens on this dedicated GCD queue and context.

The view controller with NSFetchedResultsController monitors changes in the main thread context. Changes happen in the background on the dedicated queue and context. Code monitors the NSManagedObjectContextDidSaveNotification notification and when the background thread updates an attribute value, those changes get pushed into the main thread context.

The NSFetchedResultsController is set up to sort on two sort descriptors, a "sortScore" and "date" (only one of which is being updated right now, "date", so every managed object instance has the same "sortScore" do it does not affect the sorting.)

When a "date" change happens, the NSFetchedResultsController notices it and posts the change, but it posts it as an "update" and not a "move", so that the ordering does not change as it should based on the sort descriptors and the "date" being updated in such a way that the new value should fall elsewhere in the ordering.

I am at a loss, after hours of working through and tracing the NSManagedObjectContextDidSaveNotification and every other conceivable point where something could fail, on why the fetched results controller is sending an "update" change and not a "move" change.

This app uses an older forked version of the Robbie Hanson XMPPFramework for managing the Core Data in case that is familiar.

(To compound the issue, once last night it DID work and the moves were happening on one run. I had deleted the app and started from a fresh install and it started to work. But subsequent runs did not work and deleting the app to start fresh did not fix it so that effect of starting fresh was probably a coincidence.)

I have used both logging as well as breakpoints to show that the fetched results controller is sending an "update" instead of a "move".

Here is the code.

Setting up and doing the initial setting up of the NSFetchedResultsController with this create method being called inside the init of the view controller.

@property  (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;

- (NSFetchedResultsController *)createFetchedResultsControllerUsingStorage:(XMPPCoreDataStorage *)storage entityName:(NSString *)entityName
{
    NSManagedObjectContext *context = [storage mainThreadManagedObjectContext];
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];

    NSSortDescriptor *sortDescriptorScore = [[NSSortDescriptor alloc] initWithKey:@"sortScore" ascending:NO];
    NSSortDescriptor *sortDescriptorDate = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorScore, sortDescriptorDate, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *controller = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];

    [controller setDelegate:self];

    [self updateResultsDataSetForController:controller];

    return controller;
}

- (BOOL)updateResultsDataSetForController:(NSFetchedResultsController *)controller
{
    NSError *error;
    BOOL success = [controller performFetch:&error];

    if (nil != error) {
        NSLog(@"Error fetching inbox items for the inbox view controller.  Error = %@", error);
    }

    return success;
}

The code to handle the changes in the NSFetchedResultsControllerDelegate are basically copied and pasted from the Apple Docs

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.conversationsTable beginUpdates];
}


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
    atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.conversationsTable insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                            withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.conversationsTable deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;

        default:
            break;
    }
}


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
    atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
    newIndexPath:(NSIndexPath *)newIndexPath
{

    UITableView *tableView = self.conversationsTable;

    switch(type) {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate: {
            MyAppInboxCell *cell = [tableView cellForRowAtIndexPath:indexPath];
            MyAppXMPPInboxBaseMember *inboxItem = [self.fetchedResultsController objectAtIndexPath:indexPath];

            MyAppConversation *conversation = [inboxItem conversation];
            [cell configureCell:conversation];
            }
            break;


        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                       withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}
0 Answers
Related