| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885 |
- //
- // _ASHierarchyChangeSet.m
- // AsyncDisplayKit
- //
- // Created by Adlai Holler on 9/29/15.
- //
- // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
- // This source code is licensed under the BSD-style license found in the
- // LICENSE file in the root directory of this source tree. An additional grant
- // of patent rights can be found in the PATENTS file in the same directory.
- //
- #import "_ASHierarchyChangeSet.h"
- #import "ASInternalHelpers.h"
- #import "NSIndexSet+ASHelpers.h"
- #import "ASAssert.h"
- #import "ASDisplayNode+Beta.h"
- #import "ASObjectDescriptionHelpers.h"
- #import <unordered_map>
- #import "ASDataController.h"
- #import "ASBaseDefines.h"
- // If assertions are enabled and they haven't forced us to suppress the exception,
- // then throw, otherwise log.
- #if ASDISPLAYNODE_ASSERTIONS_ENABLED
- #define ASFailUpdateValidation(...)\
- _Pragma("clang diagnostic push")\
- _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")\
- if ([ASDisplayNode suppressesInvalidCollectionUpdateExceptions]) {\
- NSLog(__VA_ARGS__);\
- } else {\
- NSLog(__VA_ARGS__);\
- [NSException raise:ASCollectionInvalidUpdateException format:__VA_ARGS__];\
- }\
- _Pragma("clang diagnostic pop")
- #else
- #define ASFailUpdateValidation(...) NSLog(__VA_ARGS__);
- #endif
- BOOL ASHierarchyChangeTypeIsFinal(_ASHierarchyChangeType changeType) {
- switch (changeType) {
- case _ASHierarchyChangeTypeInsert:
- case _ASHierarchyChangeTypeDelete:
- return YES;
- default:
- return NO;
- }
- }
- NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType)
- {
- switch (changeType) {
- case _ASHierarchyChangeTypeInsert:
- return @"Insert";
- case _ASHierarchyChangeTypeOriginalInsert:
- return @"OriginalInsert";
- case _ASHierarchyChangeTypeDelete:
- return @"Delete";
- case _ASHierarchyChangeTypeOriginalDelete:
- return @"OriginalDelete";
- case _ASHierarchyChangeTypeReload:
- return @"Reload";
- default:
- return @"(invalid)";
- }
- }
- @interface _ASHierarchySectionChange ()
- - (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions;
- /**
- On return `changes` is sorted according to the change type with changes coalesced by animationOptions
- Assumes: `changes` all have the same changeType
- */
- + (void)sortAndCoalesceSectionChanges:(NSMutableArray<_ASHierarchySectionChange *> *)changes;
- /// Returns all the indexes from all the `indexSet`s of the given `_ASHierarchySectionChange` objects.
- + (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray *)changes;
- + (NSString *)smallDescriptionForSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes;
- @end
- @interface _ASHierarchyItemChange ()
- - (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted;
- /**
- On return `changes` is sorted according to the change type with changes coalesced by animationOptions
- Assumes: `changes` all have the same changeType
- */
- + (void)sortAndCoalesceItemChanges:(NSMutableArray<_ASHierarchyItemChange *> *)changes ignoringChangesInSections:(NSIndexSet *)sections;
- + (NSString *)smallDescriptionForItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes;
- + (void)ensureItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofSameType:(_ASHierarchyChangeType)changeType;
- @end
- @interface _ASHierarchyChangeSet ()
- @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *insertItemChanges;
- @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *originalInsertItemChanges;
- @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *deleteItemChanges;
- @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *originalDeleteItemChanges;
- @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *reloadItemChanges;
- @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *insertSectionChanges;
- @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *originalInsertSectionChanges;
- @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *deleteSectionChanges;
- @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *originalDeleteSectionChanges;
- @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *reloadSectionChanges;
- @end
- @implementation _ASHierarchyChangeSet {
- std::vector<NSInteger> _oldItemCounts;
- std::vector<NSInteger> _newItemCounts;
- void (^_completionHandler)(BOOL finished);
- }
- - (instancetype)init
- {
- ASFailUpdateValidation(@"_ASHierarchyChangeSet: -init is not supported. Call -initWithOldData:");
- return [self initWithOldData:std::vector<NSInteger>()];
- }
- - (instancetype)initWithOldData:(std::vector<NSInteger>)oldItemCounts
- {
- self = [super init];
- if (self) {
- _oldItemCounts = oldItemCounts;
-
- _originalInsertItemChanges = [[NSMutableArray alloc] init];
- _insertItemChanges = [[NSMutableArray alloc] init];
- _originalDeleteItemChanges = [[NSMutableArray alloc] init];
- _deleteItemChanges = [[NSMutableArray alloc] init];
- _reloadItemChanges = [[NSMutableArray alloc] init];
-
- _originalInsertSectionChanges = [[NSMutableArray alloc] init];
- _insertSectionChanges = [[NSMutableArray alloc] init];
- _originalDeleteSectionChanges = [[NSMutableArray alloc] init];
- _deleteSectionChanges = [[NSMutableArray alloc] init];
- _reloadSectionChanges = [[NSMutableArray alloc] init];
- }
- return self;
- }
- #pragma mark External API
- - (void (^)(BOOL finished))completionHandler
- {
- void (^completionHandler)(BOOL) = _completionHandler;
- _completionHandler = nil;
- return completionHandler;
- }
- - (void)addCompletionHandler:(void (^)(BOOL))completion
- {
- [self _ensureNotCompleted];
- if (completion == nil) {
- return;
- }
- void (^oldCompletionHandler)(BOOL finished) = _completionHandler;
- _completionHandler = ^(BOOL finished) {
- if (oldCompletionHandler != nil) {
- oldCompletionHandler(finished);
- }
- completion(finished);
- };
- }
- - (void)markCompletedWithNewItemCounts:(std::vector<NSInteger>)newItemCounts
- {
- NSAssert(!_completed, @"Attempt to mark already-completed changeset as completed.");
- _completed = YES;
- _newItemCounts = newItemCounts;
- [self _sortAndCoalesceChangeArrays];
- [self _validateUpdate];
- }
- - (NSArray *)sectionChangesOfType:(_ASHierarchyChangeType)changeType
- {
- [self _ensureCompleted];
- switch (changeType) {
- case _ASHierarchyChangeTypeInsert:
- return _insertSectionChanges;
- case _ASHierarchyChangeTypeReload:
- return _reloadSectionChanges;
- case _ASHierarchyChangeTypeDelete:
- return _deleteSectionChanges;
- case _ASHierarchyChangeTypeOriginalDelete:
- return _originalDeleteSectionChanges;
- case _ASHierarchyChangeTypeOriginalInsert:
- return _originalInsertSectionChanges;
- default:
- NSAssert(NO, @"Request for section changes with invalid type: %lu", (long)changeType);
- return nil;
- }
- }
- - (NSArray *)itemChangesOfType:(_ASHierarchyChangeType)changeType
- {
- [self _ensureCompleted];
- switch (changeType) {
- case _ASHierarchyChangeTypeInsert:
- return _insertItemChanges;
- case _ASHierarchyChangeTypeReload:
- return _reloadItemChanges;
- case _ASHierarchyChangeTypeDelete:
- return _deleteItemChanges;
- case _ASHierarchyChangeTypeOriginalInsert:
- return _originalInsertItemChanges;
- case _ASHierarchyChangeTypeOriginalDelete:
- return _originalDeleteItemChanges;
- default:
- NSAssert(NO, @"Request for item changes with invalid type: %lu", (long)changeType);
- return nil;
- }
- }
- - (NSIndexSet *)indexesForItemChangesOfType:(_ASHierarchyChangeType)changeType inSection:(NSUInteger)section
- {
- [self _ensureCompleted];
- NSMutableIndexSet *result = [NSMutableIndexSet indexSet];
- for (_ASHierarchyItemChange *change in [self itemChangesOfType:changeType]) {
- [result addIndexes:[NSIndexSet as_indexSetFromIndexPaths:change.indexPaths inSection:section]];
- }
- return result;
- }
- - (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection
- {
- ASDisplayNodeAssertNotNil(_deletedSections, @"Cannot call %@ before `markCompleted` returns.", NSStringFromSelector(_cmd));
- ASDisplayNodeAssertNotNil(_insertedSections, @"Cannot call %@ before `markCompleted` returns.", NSStringFromSelector(_cmd));
- [self _ensureCompleted];
- if ([_deletedSections containsIndex:oldSection]) {
- return NSNotFound;
- }
- NSUInteger newIndex = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)];
- newIndex += [_insertedSections as_indexChangeByInsertingItemsBelowIndex:newIndex];
- return newIndex;
- }
- - (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
- {
- [self _ensureNotCompleted];
- _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalDelete indexPaths:indexPaths animationOptions:options presorted:NO];
- [_originalDeleteItemChanges addObject:change];
- }
- - (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
- {
- [self _ensureNotCompleted];
- _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalDelete indexSet:sections animationOptions:options];
- [_originalDeleteSectionChanges addObject:change];
- }
- - (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
- {
- [self _ensureNotCompleted];
- _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalInsert indexPaths:indexPaths animationOptions:options presorted:NO];
- [_originalInsertItemChanges addObject:change];
- }
- - (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
- {
- [self _ensureNotCompleted];
- _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalInsert indexSet:sections animationOptions:options];
- [_originalInsertSectionChanges addObject:change];
- }
- - (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
- {
- [self _ensureNotCompleted];
- _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexPaths:indexPaths animationOptions:options presorted:NO];
- [_reloadItemChanges addObject:change];
- }
- - (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
- {
- [self _ensureNotCompleted];
- _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexSet:sections animationOptions:options];
- [_reloadSectionChanges addObject:change];
- }
- - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath animationOptions:(ASDataControllerAnimationOptions)options
- {
- /**
- * TODO: Proper move implementation.
- */
- [self deleteItems:@[ indexPath ] animationOptions:options];
- [self insertItems:@[ newIndexPath ] animationOptions:options];
- }
- - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection animationOptions:(ASDataControllerAnimationOptions)options
- {
- /**
- * TODO: Proper move implementation.
- */
- [self deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:options];
- [self insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:options];
- }
- #pragma mark Private
- - (BOOL)_ensureNotCompleted
- {
- NSAssert(!_completed, @"Attempt to modify completed changeset %@", self);
- return !_completed;
- }
- - (BOOL)_ensureCompleted
- {
- NSAssert(_completed, @"Attempt to process incomplete changeset %@", self);
- return _completed;
- }
- - (void)_sortAndCoalesceChangeArrays
- {
- @autoreleasepool {
- // Split reloaded sections into [delete(oldIndex), insert(newIndex)]
-
- // Give these their "pre-reloads" values. Once we add in the reloads we'll re-process them.
- _deletedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_originalDeleteSectionChanges];
- _insertedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_originalInsertSectionChanges];
- for (_ASHierarchySectionChange *originalDeleteSectionChange in _originalDeleteSectionChanges) {
- [_deleteSectionChanges addObject:[originalDeleteSectionChange changeByFinalizingType]];
- }
- for (_ASHierarchySectionChange *originalInsertSectionChange in _originalInsertSectionChanges) {
- [_insertSectionChanges addObject:[originalInsertSectionChange changeByFinalizingType]];
- }
-
- for (_ASHierarchySectionChange *change in _reloadSectionChanges) {
- NSIndexSet *newSections = [change.indexSet as_indexesByMapping:^(NSUInteger idx) {
- NSUInteger newSec = [self newSectionForOldSection:idx];
- ASDisplayNodeAssert(newSec != NSNotFound, @"Request to reload and delete same section %tu", idx);
- return newSec;
- }];
-
- _ASHierarchySectionChange *deleteChange = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexSet:change.indexSet animationOptions:change.animationOptions];
- [_deleteSectionChanges addObject:deleteChange];
-
- _ASHierarchySectionChange *insertChange = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexSet:newSections animationOptions:change.animationOptions];
- [_insertSectionChanges addObject:insertChange];
- }
-
- [_ASHierarchySectionChange sortAndCoalesceSectionChanges:_deleteSectionChanges];
- [_ASHierarchySectionChange sortAndCoalesceSectionChanges:_insertSectionChanges];
- _deletedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_deleteSectionChanges];
- _insertedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_insertSectionChanges];
- // Split reloaded items into [delete(oldIndexPath), insert(newIndexPath)]
- for (_ASHierarchyItemChange *originalDeleteItemChange in _originalDeleteItemChanges) {
- [_deleteItemChanges addObject:[originalDeleteItemChange changeByFinalizingType]];
- }
- for (_ASHierarchyItemChange *originalInsertItemChange in _originalInsertItemChanges) {
- [_insertItemChanges addObject:[originalInsertItemChange changeByFinalizingType]];
- }
-
- [_ASHierarchyItemChange ensureItemChanges:_insertItemChanges ofSameType:_ASHierarchyChangeTypeInsert];
- NSDictionary *insertedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_insertItemChanges];
-
- [_ASHierarchyItemChange ensureItemChanges:_deleteItemChanges ofSameType:_ASHierarchyChangeTypeDelete];
- NSDictionary *deletedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_deleteItemChanges];
-
- for (_ASHierarchyItemChange *change in _reloadItemChanges) {
- NSAssert(change.changeType == _ASHierarchyChangeTypeReload, @"It must be a reload change to be in here");
- NSMutableArray *newIndexPaths = [NSMutableArray arrayWithCapacity:change.indexPaths.count];
-
- // Every indexPaths in the change need to update its section and/or row
- // depending on all the deletions and insertions
- // For reference, when batching reloads/deletes/inserts:
- // - delete/reload indexPaths that are passed in should all be their current indexPaths
- // - insert indexPaths that are passed in should all be their future indexPaths after deletions
- for (NSIndexPath *indexPath in change.indexPaths) {
- NSUInteger section = [self newSectionForOldSection:indexPath.section];
- NSUInteger item = indexPath.item;
-
- // Update row number based on deletions that are above the current row in the current section
- NSIndexSet *indicesDeletedInSection = deletedIndexPathsMap[@(indexPath.section)];
- item -= [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, item)];
- // Update row number based on insertions that are above the current row in the future section
- NSIndexSet *indicesInsertedInSection = insertedIndexPathsMap[@(section)];
- item += [indicesInsertedInSection as_indexChangeByInsertingItemsBelowIndex:item];
-
- NSIndexPath *newIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
- [newIndexPaths addObject:newIndexPath];
- }
-
- // All reload changes are translated into deletes and inserts
- // We delete the items that needs reload together with other deleted items, at their original index
- _ASHierarchyItemChange *deleteItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:change.indexPaths animationOptions:change.animationOptions presorted:NO];
- [_deleteItemChanges addObject:deleteItemChangeFromReloadChange];
- // We insert the items that needs reload together with other inserted items, at their future index
- _ASHierarchyItemChange *insertItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:newIndexPaths animationOptions:change.animationOptions presorted:NO];
- [_insertItemChanges addObject:insertItemChangeFromReloadChange];
- }
-
- // Ignore item deletes in reloaded/deleted sections.
- [_ASHierarchyItemChange sortAndCoalesceItemChanges:_deleteItemChanges ignoringChangesInSections:_deletedSections];
- // Ignore item inserts in reloaded(new)/inserted sections.
- [_ASHierarchyItemChange sortAndCoalesceItemChanges:_insertItemChanges ignoringChangesInSections:_insertedSections];
- }
- }
- - (void)_validateUpdate
- {
- NSIndexSet *allReloadedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_reloadSectionChanges];
-
- NSInteger newSectionCount = _newItemCounts.size();
- NSInteger oldSectionCount = _oldItemCounts.size();
-
- NSInteger insertedSectionCount = _insertedSections.count;
- NSInteger deletedSectionCount = _deletedSections.count;
- // Assert that the new section count is correct.
- if (newSectionCount != oldSectionCount + insertedSectionCount - deletedSectionCount) {
- ASFailUpdateValidation(@"Invalid number of sections. The number of sections after the update (%zd) must be equal to the number of sections before the update (%zd) plus or minus the number of sections inserted or deleted (%tu inserted, %tu deleted)", newSectionCount, oldSectionCount, insertedSectionCount, deletedSectionCount);
- return;
- }
-
- // Assert that no invalid deletes/reloads happened.
- NSInteger invalidSectionDelete = NSNotFound;
- if (oldSectionCount == 0) {
- invalidSectionDelete = _deletedSections.firstIndex;
- } else {
- invalidSectionDelete = [_deletedSections indexGreaterThanIndex:oldSectionCount - 1];
- }
- if (invalidSectionDelete != NSNotFound) {
- ASFailUpdateValidation(@"Attempt to delete section %zd but there are only %zd sections before the update.", invalidSectionDelete, oldSectionCount);
- return;
- }
-
- for (_ASHierarchyItemChange *change in _deleteItemChanges) {
- for (NSIndexPath *indexPath in change.indexPaths) {
- // Assert that item delete happened in a valid section.
- NSInteger section = indexPath.section;
- NSInteger item = indexPath.item;
- if (section >= oldSectionCount) {
- ASFailUpdateValidation(@"Attempt to delete item %zd from section %zd, but there are only %zd sections before the update.", item, section, oldSectionCount);
- return;
- }
-
- // Assert that item delete happened to a valid item.
- NSInteger oldItemCount = _oldItemCounts[section];
- if (item >= oldItemCount) {
- ASFailUpdateValidation(@"Attempt to delete item %zd from section %zd, which only contains %zd items before the update.", item, section, oldItemCount);
- return;
- }
- }
- }
-
- for (_ASHierarchyItemChange *change in _insertItemChanges) {
- for (NSIndexPath *indexPath in change.indexPaths) {
- NSInteger section = indexPath.section;
- NSInteger item = indexPath.item;
- // Assert that item insert happened in a valid section.
- if (section >= newSectionCount) {
- ASFailUpdateValidation(@"Attempt to insert item %zd into section %zd, but there are only %zd sections after the update.", item, section, newSectionCount);
- return;
- }
-
- // Assert that item delete happened to a valid item.
- NSInteger newItemCount = _newItemCounts[section];
- if (item >= newItemCount) {
- ASFailUpdateValidation(@"Attempt to insert item %zd into section %zd, which only contains %zd items after the update.", item, section, newItemCount);
- return;
- }
- }
- }
-
- // Assert that no sections were inserted out of bounds.
- NSInteger invalidSectionInsert = NSNotFound;
- if (newSectionCount == 0) {
- invalidSectionInsert = _insertedSections.firstIndex;
- } else {
- invalidSectionInsert = [_insertedSections indexGreaterThanIndex:newSectionCount - 1];
- }
- if (invalidSectionInsert != NSNotFound) {
- ASFailUpdateValidation(@"Attempt to insert section %zd but there are only %zd sections after the update.", invalidSectionInsert, newSectionCount);
- return;
- }
-
- for (NSUInteger oldSection = 0; oldSection < oldSectionCount; oldSection++) {
- NSInteger oldItemCount = _oldItemCounts[oldSection];
- // If section was reloaded, ignore.
- if ([allReloadedSections containsIndex:oldSection]) {
- continue;
- }
-
- // If section was deleted, ignore.
- NSUInteger newSection = [self newSectionForOldSection:oldSection];
- if (newSection == NSNotFound) {
- continue;
- }
-
- NSIndexSet *originalInsertedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeOriginalInsert inSection:newSection];
- NSIndexSet *originalDeletedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeOriginalDelete inSection:oldSection];
- NSIndexSet *reloadedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeReload inSection:oldSection];
-
- // Assert that no reloaded items were deleted.
- NSInteger deletedReloadedItem = [originalDeletedItems as_intersectionWithIndexes:reloadedItems].firstIndex;
- if (deletedReloadedItem != NSNotFound) {
- ASFailUpdateValidation(@"Attempt to delete and reload the same item at index path %@", [NSIndexPath indexPathForItem:deletedReloadedItem inSection:oldSection]);
- return;
- }
-
- // Assert that the new item count is correct.
- NSInteger newItemCount = _newItemCounts[newSection];
- NSInteger insertedItemCount = originalInsertedItems.count;
- NSInteger deletedItemCount = originalDeletedItems.count;
- if (newItemCount != oldItemCount + insertedItemCount - deletedItemCount) {
- ASFailUpdateValidation(@"Invalid number of items in section %zd. The number of items after the update (%zd) must be equal to the number of items before the update (%zd) plus or minus the number of items inserted or deleted (%zd inserted, %zd deleted).", oldSection, newItemCount, oldItemCount, insertedItemCount, deletedItemCount);
- return;
- }
- }
- }
- #pragma mark - Debugging (Private)
- - (NSString *)description
- {
- return ASObjectDescriptionMake(self, [self propertiesForDescription]);
- }
- - (NSString *)debugDescription
- {
- return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]);
- }
- - (NSMutableArray<NSDictionary *> *)propertiesForDescription
- {
- NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
- if (_reloadSectionChanges.count > 0) {
- [result addObject:@{ @"reloadSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_reloadSectionChanges] }];
- }
- if (_reloadItemChanges.count > 0) {
- [result addObject:@{ @"reloadItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_reloadItemChanges] }];
- }
- if (_originalDeleteSectionChanges.count > 0) {
- [result addObject:@{ @"deleteSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_originalDeleteSectionChanges] }];
- }
- if (_originalDeleteItemChanges.count > 0) {
- [result addObject:@{ @"deleteItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_originalDeleteItemChanges] }];
- }
- if (_originalInsertSectionChanges.count > 0) {
- [result addObject:@{ @"insertSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_originalInsertSectionChanges] }];
- }
- if (_originalInsertItemChanges.count > 0) {
- [result addObject:@{ @"insertItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_originalInsertItemChanges] }];
- }
- return result;
- }
- - (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
- {
- return [self propertiesForDescription];
- }
- @end
- @implementation _ASHierarchySectionChange
- - (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions
- {
- self = [super init];
- if (self) {
- ASDisplayNodeAssert(indexSet.count > 0, @"Request to create _ASHierarchySectionChange with no sections!");
- _changeType = changeType;
- _indexSet = indexSet;
- _animationOptions = animationOptions;
- }
- return self;
- }
- - (_ASHierarchySectionChange *)changeByFinalizingType
- {
- _ASHierarchyChangeType newType;
- switch (_changeType) {
- case _ASHierarchyChangeTypeOriginalInsert:
- newType = _ASHierarchyChangeTypeInsert;
- break;
- case _ASHierarchyChangeTypeOriginalDelete:
- newType = _ASHierarchyChangeTypeDelete;
- break;
- default:
- ASFailUpdateValidation(@"Attempt to finalize section change of invalid type %@.", NSStringFromASHierarchyChangeType(_changeType));
- return self;
- }
- return [[_ASHierarchySectionChange alloc] initWithChangeType:newType indexSet:_indexSet animationOptions:_animationOptions];
- }
- + (void)sortAndCoalesceSectionChanges:(NSMutableArray<_ASHierarchySectionChange *> *)changes
- {
- _ASHierarchySectionChange *firstChange = changes.firstObject;
- if (firstChange == nil) {
- return;
- }
- _ASHierarchyChangeType type = [firstChange changeType];
-
- ASDisplayNodeAssert(ASHierarchyChangeTypeIsFinal(type), @"Attempt to sort and coalesce section changes of intermediary type %@. Why?", NSStringFromASHierarchyChangeType(type));
-
- // Lookup table [Int: AnimationOptions]
- __block std::unordered_map<NSUInteger, ASDataControllerAnimationOptions> animationOptions;
-
- // All changed indexes
- NSMutableIndexSet *allIndexes = [NSMutableIndexSet new];
-
- for (_ASHierarchySectionChange *change in changes) {
- ASDataControllerAnimationOptions options = change.animationOptions;
- NSIndexSet *indexes = change.indexSet;
- [indexes enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
- for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
- animationOptions[i] = options;
- }
- }];
- [allIndexes addIndexes:indexes];
- }
-
- // Create new changes by grouping sorted changes by animation option
- NSMutableArray *result = [[NSMutableArray alloc] init];
-
- __block ASDataControllerAnimationOptions currentOptions = 0;
- NSMutableIndexSet *currentIndexes = [NSMutableIndexSet indexSet];
- BOOL reverse = type == _ASHierarchyChangeTypeDelete || type == _ASHierarchyChangeTypeOriginalDelete;
- NSEnumerationOptions options = reverse ? NSEnumerationReverse : kNilOptions;
- [allIndexes enumerateRangesWithOptions:options usingBlock:^(NSRange range, BOOL * _Nonnull stop) {
- NSInteger increment = reverse ? -1 : 1;
- NSUInteger start = reverse ? NSMaxRange(range) - 1 : range.location;
- NSInteger limit = reverse ? range.location - 1 : NSMaxRange(range);
- for (NSInteger i = start; i != limit; i += increment) {
- ASDataControllerAnimationOptions options = animationOptions[i];
-
- // End the previous group if needed.
- if (options != currentOptions && currentIndexes.count > 0) {
- _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions];
- [result addObject:change];
- [currentIndexes removeAllIndexes];
- }
-
- // Start a new group if needed.
- if (currentIndexes.count == 0) {
- currentOptions = options;
- }
-
- [currentIndexes addIndex:i];
- }
- }];
- // Finish up the last group.
- if (currentIndexes.count > 0) {
- _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions];
- [result addObject:change];
- }
- [changes setArray:result];
- }
- + (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes
- {
- NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
- for (_ASHierarchySectionChange *change in changes) {
- [indexes addIndexes:change.indexSet];
- }
- return indexes;
- }
- #pragma mark - Debugging (Private)
- + (NSString *)smallDescriptionForSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes
- {
- NSMutableIndexSet *unionIndexSet = [NSMutableIndexSet indexSet];
- for (_ASHierarchySectionChange *change in changes) {
- [unionIndexSet addIndexes:change.indexSet];
- }
- return [unionIndexSet as_smallDescription];
- }
- - (NSString *)description
- {
- return ASObjectDescriptionMake(self, [self propertiesForDescription]);
- }
- - (NSString *)debugDescription
- {
- return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]);
- }
- - (NSString *)smallDescription
- {
- return [self.indexSet as_smallDescription];
- }
- - (NSMutableArray<NSDictionary *> *)propertiesForDescription
- {
- NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
- [result addObject:@{ @"indexes" : [self.indexSet as_smallDescription] }];
- return result;
- }
- - (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
- {
- NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
- [result addObject:@{ @"anim" : @(_animationOptions) }];
- [result addObject:@{ @"type" : NSStringFromASHierarchyChangeType(_changeType) }];
- [result addObject:@{ @"indexes" : self.indexSet }];
- return result;
- }
- @end
- @implementation _ASHierarchyItemChange
- - (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted
- {
- self = [super init];
- if (self) {
- ASDisplayNodeAssert(indexPaths.count > 0, @"Request to create _ASHierarchyItemChange with no items!");
- _changeType = changeType;
- if (presorted) {
- _indexPaths = indexPaths;
- } else {
- SEL sorting = changeType == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:);
- _indexPaths = [indexPaths sortedArrayUsingSelector:sorting];
- }
- _animationOptions = animationOptions;
- }
- return self;
- }
- // Create a mapping out of changes indexPaths to a {@section : [indexSet]} fashion
- // e.g. changes: (0 - 0), (0 - 1), (2 - 5)
- // will become: {@0 : [0, 1], @2 : [5]}
- + (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray<_ASHierarchyItemChange *> *)changes
- {
- NSMutableDictionary *sectionToIndexSetMap = [NSMutableDictionary dictionary];
- for (_ASHierarchyItemChange *change in changes) {
- for (NSIndexPath *indexPath in change.indexPaths) {
- NSNumber *sectionKey = @(indexPath.section);
- NSMutableIndexSet *indexSet = sectionToIndexSetMap[sectionKey];
- if (indexSet) {
- [indexSet addIndex:indexPath.item];
- } else {
- indexSet = [NSMutableIndexSet indexSetWithIndex:indexPath.item];
- sectionToIndexSetMap[sectionKey] = indexSet;
- }
- }
- }
- return sectionToIndexSetMap;
- }
- + (void)ensureItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofSameType:(_ASHierarchyChangeType)changeType
- {
- #if ASDISPLAYNODE_ASSERTIONS_ENABLED
- for (_ASHierarchyItemChange *change in changes) {
- NSAssert(change.changeType == changeType, @"The map we created must all be of the same changeType as of now");
- }
- #endif
- }
- - (_ASHierarchyItemChange *)changeByFinalizingType
- {
- _ASHierarchyChangeType newType;
- switch (_changeType) {
- case _ASHierarchyChangeTypeOriginalInsert:
- newType = _ASHierarchyChangeTypeInsert;
- break;
- case _ASHierarchyChangeTypeOriginalDelete:
- newType = _ASHierarchyChangeTypeDelete;
- break;
- default:
- ASFailUpdateValidation(@"Attempt to finalize item change of invalid type %@.", NSStringFromASHierarchyChangeType(_changeType));
- return self;
- }
- return [[_ASHierarchyItemChange alloc] initWithChangeType:newType indexPaths:_indexPaths animationOptions:_animationOptions presorted:YES];
- }
- + (void)sortAndCoalesceItemChanges:(NSMutableArray<_ASHierarchyItemChange *> *)changes ignoringChangesInSections:(NSIndexSet *)ignoredSections
- {
- if (changes.count < 1) {
- return;
- }
-
- _ASHierarchyChangeType type = [changes.firstObject changeType];
- ASDisplayNodeAssert(ASHierarchyChangeTypeIsFinal(type), @"Attempt to sort and coalesce item changes of intermediary type %@. Why?", NSStringFromASHierarchyChangeType(type));
-
- // Lookup table [NSIndexPath: AnimationOptions]
- NSMutableDictionary *animationOptions = [NSMutableDictionary new];
-
- // All changed index paths, sorted
- NSMutableArray *allIndexPaths = [[NSMutableArray alloc] init];
-
- for (_ASHierarchyItemChange *change in changes) {
- for (NSIndexPath *indexPath in change.indexPaths) {
- if (![ignoredSections containsIndex:indexPath.section]) {
- animationOptions[indexPath] = @(change.animationOptions);
- [allIndexPaths addObject:indexPath];
- }
- }
- }
-
- SEL sorting = type == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:);
- [allIndexPaths sortUsingSelector:sorting];
- // Create new changes by grouping sorted changes by animation option
- NSMutableArray *result = [[NSMutableArray alloc] init];
- ASDataControllerAnimationOptions currentOptions = 0;
- NSMutableArray *currentIndexPaths = [NSMutableArray array];
- for (NSIndexPath *indexPath in allIndexPaths) {
- ASDataControllerAnimationOptions options = [animationOptions[indexPath] integerValue];
- // End the previous group if needed.
- if (options != currentOptions && currentIndexPaths.count > 0) {
- _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES];
- [result addObject:change];
- [currentIndexPaths removeAllObjects];
- }
- // Start a new group if needed.
- if (currentIndexPaths.count == 0) {
- currentOptions = options;
- }
- [currentIndexPaths addObject:indexPath];
- }
- // Finish up the last group.
- if (currentIndexPaths.count > 0) {
- _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES];
- [result addObject:change];
- }
- [changes setArray:result];
- }
- #pragma mark - Debugging (Private)
- + (NSString *)smallDescriptionForItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes
- {
- NSDictionary *map = [self sectionToIndexSetMapFromChanges:changes];
- NSMutableString *str = [NSMutableString stringWithString:@"{ "];
- [map enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull section, NSIndexSet * _Nonnull indexSet, BOOL * _Nonnull stop) {
- [str appendFormat:@"@%lu : %@ ", (long)section.integerValue, [indexSet as_smallDescription]];
- }];
- [str appendString:@"}"];
- return str;
- }
- - (NSString *)description
- {
- return ASObjectDescriptionMake(self, [self propertiesForDescription]);
- }
- - (NSString *)debugDescription
- {
- return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]);
- }
- - (NSMutableArray<NSDictionary *> *)propertiesForDescription
- {
- NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
- [result addObject:@{ @"indexPaths" : self.indexPaths }];
- return result;
- }
- - (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
- {
- NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
- [result addObject:@{ @"anim" : @(_animationOptions) }];
- [result addObject:@{ @"type" : NSStringFromASHierarchyChangeType(_changeType) }];
- [result addObject:@{ @"indexPaths" : self.indexPaths }];
- return result;
- }
- @end
|