Quick row insertion in a UITableView raises an NSInternalInconsistencyException

I have a UITableView that sometimes has quick insertion of new rows. The input of new lines is processed by an observer-observer who listens for an update notification, which is triggered whenever the underlying data changes. I use the @synchronized block around all changes to the data model and the actual alert about myself ... hoping that every change in incremental data (and row insertion) will be handled separately. However, there are times when this still fails. The exception will tell me that he is expecting 10 lines (based on counting from the data model), he previously had 8 lines, but the update notification only told him to insert one line (since this is the first of two high-speed notifications).

I am trying to understand how other people tend to deal with these types of situations. How do other developers alleviate the problems of having multi-threaded race conditions between two table view update operations? Should I have a more secure lock that manages update notifications (and why does @synchronized do what it should)?

Any advice is appreciated! Thanks.

Some pseudo codes:

There is a method in my model class that is called by other threads to add new rows to the table view:

- (void)addRow:(NSString *)data { @synchronized(self.arrayOfData) { NSInteger nextIndex = self.arrayData.count; [self.arrayData addObject:data]; [NSNotificationCenter.defaultCenter postNotificationName:kDataUpdatedNotification object:self userInfo:@{@"insert": @[[NSIndexPath indexPathForRow:nextIndex inSection:0]]}]; } } 

My controller class has a method like this to accept the kDataUpdatedNotification notification and actually do the row insertion:

 - (void)onDataUpdatedNotification:(NSNotification *)notification { NSDictionary *changes = notification.userInfo; [self.tableView insertRowsAtIndexPaths:changes[@"insert"] withRowAnimation:UITableViewRowAnimationBottom]; } 
+4
source share
1 answer

You will have this problem if you change your data model asynchronously with the main queue, because the delegate methods in the table view look at the current state of the data model, which may be ahead of the inserts you attach to the table view.

UPDATE

One solution is to queue your updates in the private queue, and this queue updates your data model in the main queue synchronously (I have not tested this code):

 @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 { dispatch_async(self.myDispatchQueue, ^{ dispatch_sync(dispatch_get_main_queue(), ^{ NSInteger nextIndex = self.arrayData.count; [self.arrayData addObject:data]; [NSNotificationCenter.defaultCenter postNotificationName:kDataUpdatedNotification object:self userInfo:@{@"insert": @[[NSIndexPath indexPathForRow:nextIndex inSection:0]]}]; }); }); } 

The reason you need an intermediate send queue is as follows. In the original solution (below), you get a series of blocks in the main queue that look something like this:

  • Add line N
  • Add line N + 1
  • A block placed as a table for animating row N
  • A block placed as a table for animating N + 1 strings

In step (3), the animation block is not synchronized with the table view because (2) happened first, which leads to an exception (for example, a statement fails). Thus, sending the block to add rows to the main queue synchronously from the private send queue, you get something like the following:

  • Add line N
  • A block placed as a table for animating row N
  • Add line N + 1
  • A block placed as a table for animating N + 1 strings

without delaying work queues.

ORIGINAL The solution still has problems with overlapping animations.

I think that everything will be fine if you update the data model in the main queue:

 - (void)addRow:(NSString *)data { dispatch_async(dispatch_get_main_queue(), ^{ NSInteger nextIndex = self.arrayData.count; [self.arrayData addObject:data]; [NSNotificationCenter.defaultCenter postNotificationName:kDataUpdatedNotification object:self userInfo:@{@"insert": @[[NSIndexPath indexPathForRow:nextIndex inSection:0]]}]; }); } 
+7
source

Source: https://habr.com/ru/post/1491543/


All Articles