UICollectionView performBatchUpdates + NSFetchedResultsController controllerDidChangeContent iOS crashes

Originator:jamie
Number:rdar://15262692 Date Originated:10/18/2013
Status:Open Resolved:
Product:iOS SDK Product Version:7.0.2
Classification:Crash Reproducible:Yes
 
Summary:
When the collection view is not visible (ie. collectionView.window == nil), the start of performBatchUpdates:completion: would call the collection view's private _validateItemCounts method, which would call the delegate method collectionView:numberOfItemsInSection:, which would in turn ask the fetched results controller for its number of objects.  The number of objects reported by the fetched results controller would be different than the number of items in the collection view prior to this call because the fetched results controller had just been updated.  But now, the collection view thinks it has the same number of items as the fetched results controller. 
 
It's then that the block for performBatchUpdates is called, and the collection view's items are changed based on the changes recorded during the fetched results controller's most recent callbacks.
 
At the end of the performUpdates block, the collection view again wants to know how many items it has, and again the fetched results controller at the end of the change reports the same number as it did at the beginning of performBatchUpdates, but the collection view has had items inserted, deleted, moved, etc. as a result of the performBatchUpdates block.
 
At this point, the collection view is apparently very confused.  Sometimes I would get an assertion failure about the updates, and other times I would get EXC_BAD_ACCESS crashes.

To work around this issue, I needed only to call collection view's numberOfItemsInSection: in the fetched results controller delegate method controllerWillChangeContent.  This apparently updates the collection view state sufficiently such that the subsequent performBatchUpdates call in controllerDidChangeContent does not trigger any collection view item count validation.

Steps to Reproduce:
1. Set up a collectionViewController with a fetchedResultsController. 
2. Open a second viewController with a child managed object context and add an object.
3. Save the child context which triggers the fetchedResultsController to trigger delegate methods on the collectionViewController.
4. In controllerDidChangeContent do performBatchUpdates and in the block do the insertItem on the collectionView.

Expected Results:
Expected result is that the collection view will insert the object and not crash the app.

Actual Results:
Collection view throws an exception like:
*** Assertion failure in -[UICollectionView _endItemAnimations], /SourceCache/UIKit/UIKit-2903.2/UICollectionView.m:3716
2013-10-18 11:07:53.362 Journal[15086:60b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0.  The number of items contained in an existing section after the update (7) must be equal to the number of items contained in that section before the update (7), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'

Version:
iOS 7.0.2/11A501

Notes:
See post on devforums at https://devforums.apple.com/message/908659#908659 for more details

Configuration:
Occurs in iOS 7.0.2 on iPad Mini and iPhone 5, uncertain about previous iOS versions or other hardware.

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!