Automatic UITableView using a NSFetchedResultsController while being in the background

127 views Asked by At

I am trying to nail down a bug which has crashed my App for ages. It took me however a long time to narrow down what the cause is, and I think I (almost) have it. If the App goes to the background, it runs a background task to cleanup the database, and remove articles order then two months.

If it so happens that there are articles which qualify, and the UITableView is open which shows this particular list then the App crashes, as inquiring the NSFetchedResultsController results often in the following error:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no section at index 26'

or sometimes:

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'

when calling: (_objects is the NSFetchedResultsController)

[_objects objectAtIndexPath:[NSIndexPath indexPathForRow:currentRow inSection:indexPath.section]]

triggered by

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject

So I was wondering if this is because the NSFetchedResultsController (on the main thread) is not available (properly) to the background task/thread when the App has resigned activity. Is that an explanation? And if so, how to I prevent this properly form happening?

I can prevent the ManagedObjectContect to merge the changes, but how do I make sure it is updated, and the changes reflected in the UITableView, when the App comes back to the foreground?

Only a theory. I know the crash happens form the background process, but not much more yet. Updating, removing, deleting works fine when the App is active.

UPDATED with a code correction: The calling of the background process

- (void)performMaintenance{      
        UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance

        __block UIBackgroundTaskIdentifier background_task; //Create a task object

        background_task = [application beginBackgroundTaskWithExpirationHandler: ^ {
            [application endBackgroundTask: background_task]; //Tell the system that we are done with the tasks
            background_task = UIBackgroundTaskInvalid; //Set the task to be invalid

            //System will be shutting down the app at any point in time now
        }];

        //Background tasks require you to use asyncrous tasks

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //Perform your tasks that your application requires

            NSLog(@"\n\nRunning maintenance in the background!\n\n");
            // Perform work that should be allowed to continue in background
            [self doMaintenance];

            [application endBackgroundTask: background_task]; //End the task so the system knows that you are done with what you need to perform
            background_task = UIBackgroundTaskInvalid; //Invalidate the background_task
        });
}


#pragma mark Actions
- (void)doMaintenance{
    DODataManager *data = [[DODataManager alloc] init];

    NSDateComponents *components = [[NSDateComponents alloc] init];
    components.month = -2;
    NSDate *twoMonthsBeforeNow = [[NSCalendar currentCalendar] dateByAddingComponents:components toDate:[NSDate date] options:0];

    NSFetchedResultsController* controller = [data getArticlesBeforeDate:twoMonthsBeforeNow];
    NSLog(@"Cleaning up database, removing %i items", controller.fetchedObjects.count);
    for(DOArticle* article in controller.fetchedObjects){
        [controller.managedObjectContext deleteObject:article];
    }

    // Update the unread count and unread count (if dataconenction available)
    [data updateBadge];

    [data commitChanges];

    data=nil;

    NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithObject:@"1.0" forKey:@"progress"];
    [[NSNotificationCenter defaultCenter] postNotificationName:kDialogUpdate object:dict];

    NSLog(@"Finished maintenace!");
}

The cleanup now happens properly in a separate thread, and the notifications is sent to the main thread, but still the same problem.

2014-01-27 22:16:34.541 iDomsPortalDev[1963:5107] Cleaning up database, removing 2 items
2014-01-27 22:16:36.024 iDomsPortalDev[1963:5107] Start for 3 items on object Setting!
2014-01-27 22:16:36.026 iDomsPortalDev[1963:5107] Removing 0 stat items
2014-01-27 22:16:36.029 iDomsPortalDev[1963:70b] Merging changes!
2014-01-27 22:16:36.030 iDomsPortalDev[1963:70b] Delete
1

There are 1 answers

2
Abizern On BEST ANSWER

You're calling doMaintainance from an asynchronous GCD block, and in that you are accessing a managed object context.

Managed object contexts are not thread safe