9

I've got a coredata model setup like this:

TileList <->> TileListOrder
TileListOrder <<-> Tile

And a NSFetchedResultsController created with:

NSPredicate* predicate = [NSPredicate predicateWithFormat:@"tile <> nil AND tileList <> nil AND tile.removed == 0 AND  tileList.removed == 0"];
NSSortDescriptor* sortTileListDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"tileList.order" ascending:YES] autorelease];
NSSortDescriptor* sortTileDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"order" ascending:YES] autorelease];

NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setPredicate:predicate];

[fetchRequest setSortDescriptors:sortDescriptors];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"TileListOrder" inManagedObjectContext:coreManagedObjectContext];
[fetchRequest setEntity:entity];

NSFetchedResultsController* fetchedResults = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                                                                 managedObjectContext:coreManagedObjectContext
                                                                                   sectionNameKeyPath:@"tileList.order"
                                                                                            cacheName:nil];

I also import data on a secondary NSManagedObjectContext and merge those changes back like this:

- (void) localContextDidSave:(NSNotification*) notification {
        dispatch_sync(dispatch_get_main_queue(), ^{
    [coreManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        [self didSaveModelToCoreDataPersistentStore];

    });

    [super didEndUpdatingObjectInBackgroundThread];

}

I'm adding and deleting TileListOrders objects. It starts with 6 objects and 3 sections, and when I finish the 6 original objects are deleted and 6 new ones are inserted.

Now my problem is that on the NSFetchedResultsController delegate, which get called right aftter mergeChangesFromContextDidSaveNotification:notification

-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller{

 NSLog(@"controller has %d sections, %d results:",[[controller sections] count], [[controller fetchedObjects] count]);
}

gets called TWICE the first time it prints 12 objects and 3 sections and the second time 6 objects in 3 sections.

Upon inspecting the fetchedObjects, the first time there are the 6 inserted objects PLUS the other 6 original ones that were deleted. The second time it contains the expected 6 objects in 3 sections are there.

Looking at:

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

   atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type

  newIndexPath:(NSIndexPath *)newIndexPath{
}

The first time controllerDidChangeContent is called, there are only NSFetchedResultsChangeInsert changes processed and on the second time only NSFetchedResultsChangeDelete changes.

I expected that controllerDidChangeContent would only get called after ALL the changes have been processed. If this were driving a tableView then it means that the tableview would animate twice. Once for the inserted items and another for the deleted items. The problem is that the first time the controllerDidChangeContent is called the objects in NSFetchedResultsController which were deleted and are still present in the result array now have invalid values for their properties so a tableView displaying those items would probably crash.

Anyone have an idea how to make the changes coalesce so the controllerDidChangeContent is called only ONCE after ther mergeChangesFromContextDidSaveNotification notification ?

----------- UPDATE ---------------

Some more info.

I'm just obvserving NSManagedObjectContextDidSaveNotification and the localContextDidSave is called only once.

NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self selector:@selector(localContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:managedObjectContext];

Here's the stack traces. The first time controllerDidChangeContent is called, only changes relating to INSERTED objects are reported and deleted objects are STILL present in the NSFetchedResultsController results. As you can see it is triggered by

#0  0x001fb735 in -[HomeViewController controllerDidChangeContent:] 
#1  0x02947f9a in -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] ()
#2  0x022d34f9 in __57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_0 ()
#3  0x02e1c0c5 in ___CFXNotificationPost_block_invoke_0 ()
#4  0x02d76efa in _CFXNotificationPost ()
#5  0x02207bb2 in -[NSNotificationCenter postNotificationName:object:userInfo:] ()
#6  0x0285a163 in -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] ()
#7  0x0286dc71 in -[NSManagedObjectContext _mergeChangesFromDidSaveDictionary:usingObjectIDs:] ()
#8  0x0286ce2e in -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] ()
#9  0x00044be2 in __50-[CoreDataUpdateableObject localContextDidSave:]_block_invoke 
#10 0x0306f731 in _dispatch_barrier_sync_f_slow_invoke ()
#11 0x0307e014 in _dispatch_client_callout ()
#12 0x0306e7d5 in _dispatch_main_queue_callback_4CF ()
#13 0x02d68af5 in __CFRunLoopRun ()
#14 0x02d67f44 in CFRunLoopRunSpecific ()
#15 0x02d67e1b in CFRunLoopRunInMode ()
#16 0x038617e3 in GSEventRunModal ()
#17 0x03861668 in GSEventRun ()
#18 0x01756ffc in UIApplicationMain ()
#19 0x000022c6 in main 

The second time controllerDidChangeContent is called the stack trace is:

#0  0x001fb735 in -[HomeViewController controllerDidChangeContent:] 
#1  0x02947f9a in -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] ()
#2  0x022d34f9 in __57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_0 ()
#3  0x02e1c0c5 in ___CFXNotificationPost_block_invoke_0 ()
#4  0x02d76efa in _CFXNotificationPost ()
#5  0x02207bb2 in -[NSNotificationCenter postNotificationName:object:userInfo:] ()
#6  0x0285a163 in -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] ()
#7  0x028f3d2f in -[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:] ()
#8  0x02855596 in -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] ()
#9  0x02854869 in -[NSManagedObjectContext processPendingChanges] ()
#10 0x02828e38 in _performRunLoopAction ()
#11 0x02d8aafe in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ ()
#12 0x02d8aa3d in __CFRunLoopDoObservers ()
#13 0x02d687c2 in __CFRunLoopRun ()
#14 0x02d67f44 in CFRunLoopRunSpecific ()
#15 0x02d67e1b in CFRunLoopRunInMode ()
#16 0x038617e3 in GSEventRunModal ()
#17 0x03861668 in GSEventRun ()
#18 0x01756ffc in UIApplicationMain ()
#19 0x000022c6 in main

And this time it is triggered by:[NSManagedObjectContext processPendingChanges] () This second time the NSFetchedResultsController results actually have the desired state: the deleted objects are removed and the inserted ones are present.

My issue here is that the first mergeChanges should trigger the controllerWillChangeContent with the coallesced changes and the results should therefore be in a consistent state.

From the original code posted I did add a [coreManagedObjectContext processPendingChanges]; after [coreManagedObjectContext mergeChangesFromContextDidSaveNotification:notification]; because without this the second call is only triggered in the next run of the runloop.

I also tryed [coreManagedObjectContext setPropagatesDeletesAtEndOfEvent:NO]; but the behaviour is the same. Deleted objects are only removed in the second call.

Sérgio
  • 101
  • 3

0 Answers0