1

I have an async worker-thread that communicate with a backend and, if needed, create new objects in my CoreData model, or simply make changes on existing objects if needed to reflect the "server-truth", especially an attribute (integer) that I call status.

Every time such a change to an object is done, it's triggered via NSNotificationCenter to all UI subscribers, among them a UITableViewController that hold a nice list of all objects, using sections to group the objects by their status. Hence, all objects with status 0 are placed in one section, all objects with status 1 is placed in another section, etc...

Since it's an async worker-thread, I've setup my CoreData-stack to have a main-context connected to the PSC, whereas each individual async thread has it's own private context where changes are merged up to the main context and then stored persistently.

Problem

The async worker-thread that detect a change when communicating with the backend, will after updating the CoreData-model via it's private context, run the postNotification code

[[NSNotificationCenter defaultCenter] postNotificationName:@"ObjectChanged"
                                                    object:self
                                                  userInfo:userInfo];

... which in turn results in all registered subscribers begin triggered by the same async thread. This makes things a little strange since UI-stuff must be invoked in main-thread, and my UI (holding the table view with sections for each status) would, when the postNotification is triggered, like to delete/insert some cells in the tableView to reflect the detected changes.

But, since I cannot directly just inject the UI-changes, I prepare the added and deleted NSIndexPaths in a dictionary that is sent to be executed in the main-thread;

NSMutableArray* addedIndexPaths = [[NSMutableArray alloc] init];
NSMutableArray* removedIndexPaths = [[NSMutableArray alloc] init];

//... based on some logic, remove and/or add
[addedIndexPaths addObject:[NSIndexPath indexPathForRow:xxx inSection:yyy];  

...

[removedIndexPaths addObject:[NSIndexPath indexPathForRow:xxx inSection:yyy];         

...

//Anything to animate...?
if (addedIndexPaths.count > 0 || removedIndexPaths.count > 0)
{
    NSDictionary* animParams = [NSDictionary dictionaryWithObjectsAndKeys:
                                    addedIndexPaths, @"added",
                                    removedIndexPaths, @"removed",
                                    nil];

    [self performSelectorOnMainThread:@selector(updateTableViewWithChanges:) withObject:animParams waitUntilDone:YES];
}

Which finally take us to the updateTableViewWithChanges function, being run by the main-thread;

- (void)updateTableViewWithChanges:(NSDictionary *)animParams
{
    NSArray* addedIndexPaths = [animParams objectForKey:@"added"];
    NSArray* removedIndexPaths = [animParams objectForKey:@"removed"];

    //Now, finally -- let's update UI!
    [self.tableView beginUpdates];
    if ([addedIndexPaths count] > 0)
        [self.tableView insertRowsAtIndexPaths:addedIndexPaths withRowAnimation:UITableViewRowAnimationTop];
    if ([removedIndexPaths count] > 0)
        [self.tableView deleteRowsAtIndexPaths:removedIndexPaths withRowAnimation:UITableViewRowAnimationTop];
    [self.tableView endUpdates];
}

All is good, most of the time, but then once in a while the app crashes due to inconsistency during the table update;

* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

I'm suspecting that this is due to the main-thread deciding to update it's UI while my "did-detect-changes" code is running, resulting in an UI-state that already IS up-2-date, which imply that when I'm calling the dynamic code of "adding another one at NSIndexPath xxx", this does not add-up.

Do you have any alternative approach to achieving what I need to do? How else would you go about and implement a async-thread updating state that you want to be reflected in the UI while still running the worker-thread and incrementally step-by-step update the UI? Am I barking up the wrong tree entirely, or just missing something along the way?

Looking forward to your thoughts and ideas, and if you're still reading -- thank you ;)

Edit (show code for how I fetch data from CoreData, since I suspect timing to the the root pif this crash)..

The private context held by the async worked thread is merged to main contact and then saved in accordance with recommendations (using performBlockAndWait), but perhaps the actual save done by the worker-thread just before notifying subscribers that things has changed, is taking time (even though I'm using performBlockAndWait, and not performBlock), and that when the main context is used to fetch the object for rendering, it's not consistent?

Here's code for when I read/fetch an object (by its unique GUID that is just an attribute);

NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                        guid, @"GUID", nil];

NSFetchRequest *fetchRequest = [self.managedObjectModel fetchRequestFromTemplateWithName:@"FindObjectByGuid" substitutionVariables:substitutionDictionary];

//Run the fetch!
NSError *error = nil;

//Uses proper context depending on if it's a worker thread or the main context/UI
NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
if (error == nil)
{
    //Great! We did it!
    NSLog(@"Great! Success fetched %lu object(s) from PS mathing given GUID", (unsigned long)[results count]);
    if ([results count] > 0)
        object = [results objectAtIndex:0];
}

/Markus

Markus Millfjord
  • 707
  • 1
  • 6
  • 26
  • The error indicates that you called `insertRowsAtIndexPaths` with one index path but you actually removed one row from your data source. So it seems one half of your process if reversed. – rmaddy Mar 01 '14 at 23:54
  • If you're doing multiple, rapid asynchronous updates, you've got to take care to avoid them overlapping. See [this discussion](http://stackoverflow.com/questions/17665916/rapid-row-insertion-into-uitableview-causes-nsinternalinconsistencyexception/17666209#17666209) and [this one](http://stackoverflow.com/questions/18108095/whats-the-best-way-to-ensure-a-uitableview-reloads-atomically/18111015#18111015). Also, have you considered using `NSFetchedResultsController` for your Core Data/table view integration? – Timothy Moose Mar 02 '14 at 00:58
  • @maddy; sorry for not being more clear. Due the current architecture of having a worker-thread async communicating with the backend and detecting changes that are posted as notifications, each such notification only contain one change; an insert OR a delete, never both. My code in my post was more for conceptual understanding. The crash-log was taken from a single insert where there UI had knowledge of 1 object, and then inserted one more which should have resulted in 2 objects. But it's not. Instead everything is reversed, but usually works as intended though. Smells like a timing issue? – Markus Millfjord Mar 02 '14 at 06:00
  • @TimothyMoose; I've updated the post with my CoreData fetch-code, and no -- I'm not using a `NSFetchedResultsController`. I thought that I was avoiding overlapping by a) using `performBlockAndWait`to save the main context from the async worked thread, and b) using `performSelectorOnMainThread` with `waitUntilDone:YES` in the notification function so that the async thread wait until UI was done...? Regardlessm I'll look at your suggested reading. Thx! – Markus Millfjord Mar 02 '14 at 06:16
  • @TimothyMoose; I've read through your suggested reading. First, I've focused on the "UI-stuff" *after* adding/changing the new data, whereas your suggested solutions is to make sure objects are added *and* notified in a separate queue (using main-thread to do the work). I see your point, but I'm having some conceptual issues to apply it to my architecture since I use main/privateQueueContexts to merge changes from private context to main contexts, using ´performBlock` and CoreData. Your approach would basically replace using `performBlock`and private contexts, wouldn't it? – Markus Millfjord Mar 02 '14 at 07:53
  • Sorry, I'm kinda lost here. I'd recommend binding your table view using `NSFetchedResultsController` and listening to `NSManagedObjectContextDidSaveNotification` from the main context rather than having the worker contexts post notifications. – Timothy Moose Mar 02 '14 at 08:31
  • I've debugged and traced and NSLogged and teared what's left of my hair over this one, so I'm more than happy to try a completely different approach ;) That said, using a `NSFetchedResultsController`, the worker thread save context "as normal" which eventually will propagate to the main context, triggering the `NSManagedObjectContextDidSaveNotification`-- but how does the UI know "what's new" so that I can keep the dynamic cell animation instead of just firing a `reloadData` when data changes? – Markus Millfjord Mar 02 '14 at 08:37
  • @TimothyMoose; never mind me... ;) I've never used `NSFetchedResultsController`s previously, but just skimming through the Apple doc indicate that it's the obvious way to go. Thank you for the guidance! I'd gotten stuck in my perspective. – Markus Millfjord Mar 02 '14 at 11:50

1 Answers1

1

Well, time to sum things up. Being fairly new to iOS, I'm used to having to do everything myself from scratch, and obviously I modelled and implemented a publish/subscribe-based mechanism and spent many hours to ensure that it was generic and automatically compared added/changes objects in CoreData with what was already there, extracting indexes in NSOrderedSets and prepared nice notification messages with all that the receiving UI might want to properly animate a table view...

But... besides the fact that I never got a fully thread-safe implementation (I'm suspecting CoreData save to PCS timings and/or multiple changes to the same object from different async threads), @Timothy Moose pointed me towards NSFetchedResultsController which act on the CoreData main context (if setup to do so, of course) and delegates nice hooks back to my UITableView, very much similar to what I was doing myself in my publish/subscribe-notifications -- but moreover, it just works.

Instead of manually myself write code to extract subsets of objects where relations and attributes match certain conditions, my NSFetchRequest's predicate now does that for me, and tells me exactly what has changed, and how I should modify my UITableView accordingly.

Bonus; I could happily get rid of a LOT of code and simply trust that I'm always showing the current state of my CoreData model. Regardless of how many async worker threads I have, and regardless of what they do - I'll get the proper changes delegated nicely when they're stored/merged from their corresponding private contexts up into the main-context.

Markus Millfjord
  • 707
  • 1
  • 6
  • 26