
To visualize arbitrary data arrays, Apple gave us
UITableView tables for one-dimensional visualizations and a
UICollectionView collection for more complex cases. For example, in iFunny every day tens of thousands of users publish and send “memesiki”. The application constantly works with various lists: memes, users, tags, correspondence, etc.
The task of displaying a list is quite common, and it is fairly easy to program. However, everything becomes much more complicated if this list changes dynamically. Unexpectedly, catching an
NSInternalInconsistencyException after another update of the contents of a table or collection is a dubious pleasure. Let's figure out how to avoid this.

So, we have a standard task: to load and display the first pack of data about kittens and then, as you view, load the next portion of content, adding new elements to the end of the table. The following is an example of a UITableView, but the described mechanics are also relevant for UICollectionView.
The
Model object stores the full array of currently loaded data.
ViewController with
UITableViewDataSource functions builds UITableView cells. Each element of the array of kittens models corresponds to a table cell in the ViewController.
')
#pragma mark - UITableViewDataSource -(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section { return self.model.kittiesCount; } -(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"KittiesCell" forIndexPath:indexPath]; NSString *kittyName = [self.model kittyAtIndex:indexPath.row]; cell.textLabel.text = [NSString stringWithFormat:@"kitty '%@'", kittyName]; return cell; }
ViewController requests the next portion of kittens from the model. Having loaded the content, the model adds them to its array and then notifies the controller about updating the data and the need to add elements to the table.

It looks logical, and you, probably, met many similar implementations in different examples on working with tables. However, in practice we have to face a number of problems.
First, the code in ViewController, which inserts successive elements into the table, is repeated for each table you write. Code duplication is bad, because nobody will argue with that?
Secondly, what will happen in a more difficult situation, when, for example, it will be necessary to remove an element from a table or change the order of elements in an array? If these operations are performed at the same time, there is a great chance to get an NSInternalInconsistencyException like this:
Terminating app. NSNternalInconsistencyException ', reason:' Terminating app due to uncaught exception '. If you want to have the text (0 inserted, 1 deleted). '
Let's look at the example from
developer.apple.com (it’s true for a UICollectionView, but the essence is the same) :
[self.collectionView performBatchUpdates:^{ NSArray* itemPaths = [self.collectionView indexPathsForSelectedItems];
According to Apple's documentation, cells from Data Source should be updated inside the performBatchUpdates block. In our case, the update of the model (in fact, the data source UITableViewDataSource for our table) is not atomically executed, that is, outside the code block, limited to the beginUpdates and endUpdates calls. So, on the basis of laconic descriptions in the Apple documentation, we can formulate 3 rules for modifying collections and tables:
- it is necessary to update the Data Source inside the update block of the table or collection itself;
- when simultaneously removing some elements and adding others, the unnecessary elements are removed first, then the missing elements are inserted;
- cannot be interchanged and at the same time add / delete items.
With the theory sorted out. Now that we have formulated these principles, we will try to reflect them in the code. Below is a description of the protocol of a modifier object of a table, collection, or some other View for displaying a set of objects.
The modifier should be able to update, delete or insert elements in the View, as well as swap cells if necessary. In addition, each function as one of the parameters takes the Data Source modification block so that it will atomically update both the model and the view. For example, the multipleItemsViewModifyBlock block returns an array of indexes that have been updated (deleted or added) in the model array, and therefore, the corresponding cells must also be updated (removed or added) in View. When calling this function, it is necessary to take into account the sequence of calling modification blocks: first updateBlock, then deleteBlock, and finally, insertBlock.
typedef NSArray<NSIndexPath *> *(^multipleItemsViewModifyBlock)(void); @protocol TRMultipleItemsViewModifierProtocol @required @property (nonatomic, weak) NSObject<TRMultipleItemsViewModifierDelegate> *delegate; /** * Method performs animated model and view modifications atomically. * Modification order: * 1. existing items are updating * 2. exhausted items are deleting * 3. new items are inserting. */ - (void)modifyAnimatedWithUpdateBlock:(multipleItemsViewModifyBlock)updateBlock deleteBlock:(multipleItemsViewModifyBlock)deleteBlock insertBlock:(multipleItemsViewModifyBlock)insertBlock; // Move cells in view - (void)modifyAnimatedWithMoveBlock:(NSArray<TRMoveItemInfo *> *(^)(void))moveBlock; // Atomically view and model modifying without any animation - (void)modifyNotAnimatedWithBlock:(void (^)(void))modifyBlock; @end
The
MultipleItemsViewModify library, which is easily installed via CocoaPods, also contains 2 implementations of the described protocol for UITableView (TRTableViewModifier) ​​and UICollectionView (TRCollectionViewModifier).
The interaction between the ViewController and Model of our kitten example now looks like this:

ViewController, in addition to the table itself, stores its viewModifier modifier, and the Model object stores a weak reference to this modifier. The model, loading a new portion of kittens, in the main thread calls the method of animated update of the table. The insertBlock block that is passed to this function performs a modification of the internal kitties array.
Using modifiers (TRCollectionViewModifier and TRTableViewModifier) ​​solves the problem of inconsistent collection modifications, prompts the developer how to update the data in the array. In addition, the modifier separates insertion, deletion, and relocation of table elements. And the amount of code in the View Controller noticeably diminished. Not bad, right?
On this, perhaps, finish. I will be glad to answer your questions and comments in the comments!
Links in the article:
→
Repository with an example;
→ Apple documentation of interest
here .