Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView endUpdates taking very long time when CoreData updates

I have a Core Data app that displays a large amount of instances in an UITableView.

We have a mainQueueContext running in the main thread and a privateQueueContext running in a background thread.

We load the data in the UITableView without problems, but when we load a refreshed version from the API we save it to the privateQueueContext and merge it in the mainQueueContext

[self.mainQueueContext mergeChangesFromContextDidSaveNotification:notification];

It takes a few seconds to trigger the call to

-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller

Which trigger the call to:

[self.tableView endUpdates];

Then the App hangs and CPU goes to 100% and memory begins to rise indefinitely until it runs out of memory. (see picture below)

CPU during the processing

Memory consumption during endUpdates

With less than 1000 elements this problem does not happen. I was wondering if any of you saw something like this.

Edit

Here's some data from instruments Instruments data showing endUpdates

like image 532
ppaulojr Avatar asked Apr 27 '16 21:04

ppaulojr


3 Answers

One improvement you can do is to use fetchBatchSize.

From Apple Doc"

When the fetch is executed, the entire request is evaluated and the identities of all matching objects recorded, but no more than batchSize objects’ data will be fetched from the persistent store at a time. The array returned from executing the request will be a proxy object that transparently faults batches on demand. (In database terms, this is an in-memory cursor.)

You could also use fetchLimit and fetchOffset to implement pagination.

Cheers, Alessandro

like image 131
ubiAle Avatar answered Nov 19 '22 21:11

ubiAle


I have experienced this problem too. I ended up gathering all updated into array on controller: didChangeObject: recording index path, NSFetchedResultsChangeType, and object itself. On controllerDidChangeContent: I was looking if its count is larger than X, if it is then just reload whole table (it's much more efficient) and if it's not then apply all changes normally as if it was happening in didChangeObject: method. It just doesn't make sense to insert/delete/move so much cells, as user won't see most of these changes anyway.

I hope this approach will help you!

like image 22
wirrwarr Avatar answered Nov 19 '22 23:11

wirrwarr


I faced a similar problem. In my case the solution was to remove setting explicitly rowHeight of the tableView to UITableViewAutomaticDimension:

tableView.rowHeight = UITableViewAutomaticDimension

I used instead the delegate tableView(_:heightForRowAt:) Although the documentation of rowHeight seems to state the opposite:

There are performance implications to using tableView(:heightForRowAt:) instead of rowHeight. Every time a table view is displayed, it calls tableView(:heightForRowAt:) on the delegate for each of its rows, which can result in a significant performance problem with table views having a large number of rows (approximately 1000 or more).

like image 1
Lorex Avatar answered Nov 19 '22 21:11

Lorex