Nick Locking
Nick Locking

Reputation: 2141

UICollectionView locks main thread for ~10 seconds on performBatchUpdates

I have a collectionview with 300 cells, driven by an NSFetchedResultsController. Every so often, all of the objects update, so I receive delegate messages telling me so, and I let the collection view handle the updates as I would a tableview. Unfortunately it locks the main thread for a few seconds every time this happens... I'm not sure why. Here's my code:

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

    [self.cascadeViewController.collectionView performBatchUpdates:^{

    NSLog(@"performingBatchUpdates");

    for (NSDictionary *change in self.changes) {

        [change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {

            NSFetchedResultsChangeType type = [key unsignedIntegerValue];

            if (type == NSFetchedResultsChangeInsert) {

                [self.cascadeViewController.collectionView insertItemsAtIndexPaths:@[obj]];

            } else if (type == NSFetchedResultsChangeDelete) {

                [self.cascadeViewController.collectionView deleteItemsAtIndexPaths:@[obj]];

            } else if (type == NSFetchedResultsChangeUpdate) {

                [self.cascadeViewController.collectionView reloadItemsAtIndexPaths:@[obj]];

            } else if (type == NSFetchedResultsChangeMove) {

                [self.cascadeViewController.collectionView moveItemAtIndexPath:obj[0] toIndexPath:obj[1]];

            }

        }];
    }

    NSLog(@"performingBatchUpdates end");

} completion:^(BOOL finished) {

    NSLog(@"completion");

    // TODO: implement
    //    [self configureMessageAndFooterView];

}];

NSLog(@"end of method");

[self.changes removeAllObjects];

}

What's going on here? All 300 objects updating at once is not going to happen constantly in my app's real-life execution but enough that I need to worry about it. I'm using a stock UICollectionViewFlowLayout - do I need to do something more custom?

Upvotes: 2

Views: 2094

Answers (2)

Xavier Jurado
Xavier Jurado

Reputation: 61

Had the same issue with performBatchUpdates:completion: locking the main thread for seconds in a collection view of just ~100 elements.

After spending way too much time on the issue I found a solution: ensure the cell's size (as returned in -collectionView:layout:sizeForItemAtIndexPath: or defined via the itemSize property of your layout) has not a fractional value. I solved my performance issues by applying floor on the computed height of my cells.

That being said, I have no idea why this happens. By looking at the stack trace of our profiled runs, a lot of time is spent in -[UIViewCollectionViewUpdate _computeGaps], which in turns invokes -[NSArray sortedArrayUsingSelector:] hundreds or even thousands of times (as well as CFSortIndexes, __CFSimpleMergeSort…). By just using an integer value for the height of our cells, sortedArrayUsingSelector is invoked less than 10 times and the whole process completes in a fraction of a second.

Upvotes: 5

Timothy Moose
Timothy Moose

Reputation: 9915

I vaguely recall seeing behavior like this before, but I don't have a solution for the NSFetchedResultsController + UICollectionViewFlowLayout combo because we stopped using both of those classes due to a multitude of issues. You might consider checking out the alternatives we open sourced:

  1. TLIndexPathTools as a replacement for NSFetchedResultsController. It provides a TLIndexPathController class that is very similar to NSFetchedResultsController except it also works with plain arrays and it can do animated sorting an filtering (unlike NSFetchedResultsController. There are numerous sample projects, including a Core Data one.
  2. VCollectionViewGridLayout as a replacement for UICollectionViewFlowLayout. It is a uniform, vertical scrolling grid, so it isn't as flexible as UICollectionViewFlowLayout, but the animations are generally much better in most cases it does sticky headers (like UITableView headers). There are a couple of sample projects that let you toggle between UICollectionViewFlowLayout and VCollectionViewGridLayout to see the improvement.

We have an iPad app with a grid-like collection view containing around 1000 items and the above gives us great performance and nice smooth animation as our Core Data database updates in the background.

Upvotes: 0

Related Questions