I'v got a UITableView whose dataSource updated at random intervals in a very short period of time. As more objects are discovered, they are added to the tableView's data source and I insert the specific indexPath:
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
The data source is located in a manager class, and a notification is posted when it changes.
- (void)addObjectToDataSource:(NSObject*)object {
[self.dataSource addObject:object];
[[NSNotificationCenter defaultCenter] postNotification:@"dataSourceUpdate" object:nil];
}
The viewController updates the tableView when it receives this notification.
- (void)handleDataSourceUpdate:(NSNotification*)notification {
NSObject *object = notification.userInfo[@"object"];
NSIndexPath *indexPath = [self indexPathForObject:object];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
}
This works fine, but I noticed that in some cases, a second object is discovered just as the first one is calling endUpdates, and I get an exception claiming I have two objects in my data source when the tableView was expecting one.
I was wondering if anyone has figured out a better way to atomically insert rows into a tableView. I was thinking of putting a @synchronized(self.tableView) block around the update, but I'd like to avoid that if possible because it is expensive.
The method I've recommended is to create a private queue for synchronously posting batch updates onto the main queue (where addRow
is a method that inserts an item into the data model at a given indexPath):
@interface MyModelClass ()
@property (strong, nonatomic) dispatch_queue_t myDispatchQueue;
@end
@implementation MyModelClass
- (dispatch_queue_t)myDispatchQueue
{
if (_myDispatchQueue == nil) {
_myDispatchQueue = dispatch_queue_create("myDispatchQueue", NULL);
}
return _myDispatchQueue;
}
- (void)addRow:(NSString *)data atIndexPath:(NSIndexPath *)indexPath
{
dispatch_async(self.myDispatchQueue, ^{
dispatch_sync(dispatch_get_main_queue(), ^{
//update the data model here
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
});
});
}
By doing it this way, you don't block any other threads and the block-based approach ensures that the table view's animation blocks (the ones that are throwing the exceptions) get executed in the right order. There is a more detailed explanation in Rapid row insertion into UITableView causes NSInternalInconsistencyException.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With