thatjuan thatjuan - 1 month ago 23
Swift Question

How to prevent NSInternalInconsistencyException with Realm and UICollectionView

I have a UICollectionView that is populated from Realm. Some users, seemingly at random, get a NSInternalInconsistencyException stating something like


Invalid update: invalid number of items in section 0. The number of
items contained in an existing section after the update (73) must be
equal to the number of items contained in that section before the
update (73), 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).


My code is based on Realm's collection example. It selects and filters some records:

self.assets = realm.objects(Asset.self).filter("is_deleted = false")


And then it subscribes to and handles notifications:

self.assetsNotificationToken = self.assets!.addNotificationBlock(){ [weak self] (changes: RealmCollectionChange) in

guard let collectionView = self!.collectionView else { return }

guard let strongSelf = self else { return }

switch changes {

case .Initial:

collectionView.reloadData()

case .Update(let _, let deletions, let insertions, let modifications):

strongSelf.collectionView?.performBatchUpdates({

collectionView.insertItemsAtIndexPaths(insertions.map { NSIndexPath(forRow: $0, inSection: 0) })

collectionView.reloadItemsAtIndexPaths(modifications.map { NSIndexPath(forRow: $0, inSection: 0) })

collectionView.deleteItemsAtIndexPaths(deletions.map { NSIndexPath(forRow: $0, inSection: 0) })

}, completion: nil)

case .Error(let error):

log.error(error.localizedDescription)
break

}

}


The count comes from:

override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

guard assets != nil else {
return 0;
}

return assets!.count

}


I later switched to RealmGridController

Unable to find the source of the crash, I switched to RealmGridController. It's a package written by a Realm core contributor and it encapsulates all of the standard functionality needed for working with realm + CollectionViews.

It seemed to work, and then I started seeing the exact same crash.


Fatal Exception: NSInternalInconsistencyException Invalid update:
invalid number of items in section 0. The number of items contained in
an existing section after the update (78) must be equal to the number
of items contained in that section before the update (78), plus or
minus the number of items inserted or deleted from that section (2
inserted, 0 deleted) and plus or minus the number of items moved into
or out of that section (0 moved in, 0 moved out).


Fatal Exception: NSInternalInconsistencyException
0 CoreFoundation 0x1839dadb0 __exceptionPreprocess
1 libobjc.A.dylib 0x18303ff80 objc_exception_throw
2 CoreFoundation 0x1839dac80 +[NSException raise:format:]
3 Foundation 0x184360154 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]
4 UIKit 0x18938b00c -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:]
5 UIKit 0x18938e464 -[UICollectionView _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:]
6 UIKit 0x18938e2e0 -[UICollectionView _performBatchUpdates:completion:invalidationContext:]
7 UIKit 0x188d2c2a4 -[UICollectionView performBatchUpdates:completion:]
8 RealmGridController 0x1014a8340 specialized RealmGridController.controllerDidChangeContent(RBQFetchedResultsController) -> () (RealmGridController.swift:316)
9 RealmGridController 0x1014a687c @objc RealmGridController.controllerDidChangeContent(RBQFetchedResultsController) -> () (RealmGridController.swift)
10 RBQFetchedResultsController 0x100ff8edc __112-[RBQFetchedResultsController calculateChangesWithAddedSafeObjects:deletedSafeObjects:changedSafeObjects:realm:]_block_invoke.433 (RBQFetchedResultsController.m:842)
11 libdispatch.dylib 0x1834254bc _dispatch_call_block_and_release

TiM TiM
Answer

RealmGridController is a library that was written before Realm supported fine-grained notifications and so it implements a lot of custom logic to get the same results. I'd strongly recommend you revert back to the original logic you had as that one is deeply integrated into Realm itself.

One important thing to remember is that Realm query results are live objects; they automatically update on each run loop iteration to include any changes that have since happened to it. As a result, the contents of self.assets should always be a direct 1-to-1 correlation of the content being displayed by your collection view. The fine-grained notifications notification only serves as a mechanism to update any outdated UI elements, and by the time it is called, self.assets will already be in the up-to-date state.

You may need to provide some more information on how your app modifies the contents of self.assets as it runs. How are elements added and deleted? Is any concurrency occurring in your app here?

You need to be very careful not to do any manual UI updates, as any unexpected divergences from the collection view's previous state and the what Realm is assuming it needs to be updated will generate these inconsistency exceptions.