diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h index fa67235957..b1f39cbf15 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h @@ -34,12 +34,20 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) { @interface _ASHierarchyChangeSet : NSObject +/// @precondition The change set must be completed. @property (nonatomic, strong, readonly) NSIndexSet *deletedSections; +/// @precondition The change set must be completed. @property (nonatomic, strong, readonly) NSIndexSet *insertedSections; +/// @precondition The change set must be completed. @property (nonatomic, strong, readonly) NSIndexSet *reloadedSections; -@property (nonatomic, strong, readonly) NSArray *insertedItems; -@property (nonatomic, strong, readonly) NSArray *deletedItems; -@property (nonatomic, strong, readonly) NSArray *reloadedItems; + +/** + Get the section index after the update for the given section before the update. + + @precondition The change set must be completed. + @returns The new section index, or NSNotFound if the given section was deleted. + */ +- (NSInteger)newSectionForOldSection:(NSInteger)oldSection; @property (nonatomic, readonly) BOOL completed; diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m index ca6040f18c..bd820e9cd6 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.m @@ -17,6 +17,9 @@ - (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:( Assumes: `changes` is [_ASHierarchySectionChange] all with the same changeType */ + (void)sortAndCoalesceChanges:(NSMutableArray *)changes; + +/// Returns all the indexes from all the `indexSet`s of the given `_ASHierarchySectionChange` objects. ++ (NSMutableIndexSet *)allIndexesInChanges:(NSArray *)changes; @end @interface _ASHierarchyItemChange () @@ -40,26 +43,12 @@ @interface _ASHierarchyChangeSet () @end -@implementation _ASHierarchyChangeSet { - NSMutableIndexSet *_deletedSections; - NSMutableIndexSet *_insertedSections; - NSMutableIndexSet *_reloadedSections; - NSMutableArray *_insertedItems; - NSMutableArray *_deletedItems; - NSMutableArray *_reloadedItems; -} +@implementation _ASHierarchyChangeSet - (instancetype)init { self = [super init]; if (self) { - _deletedSections = [NSMutableIndexSet new]; - _insertedSections = [NSMutableIndexSet new]; - _reloadedSections = [NSMutableIndexSet new]; - - _deletedItems = [NSMutableArray new]; - _insertedItems = [NSMutableArray new]; - _reloadedItems = [NSMutableArray new]; _insertItemChanges = [NSMutableArray new]; _deleteItemChanges = [NSMutableArray new]; @@ -110,6 +99,17 @@ - (NSArray *)itemChangesOfType:(_ASHierarchyChangeType)changeType } } +- (NSInteger)newSectionForOldSection:(NSInteger)oldSection +{ + [self _ensureCompleted]; + if ([_deletedSections containsIndex:oldSection]) { + return NSNotFound; + } + + NSInteger indexAfterDeletes = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)]; + return indexAfterDeletes + [_insertedSections countOfIndexesInRange:NSMakeRange(0, indexAfterDeletes)]; +} + - (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options { [self _ensureNotCompleted]; @@ -172,9 +172,32 @@ - (void)_sortAndCoalesceChangeArrays [_ASHierarchySectionChange sortAndCoalesceChanges:_deleteSectionChanges]; [_ASHierarchySectionChange sortAndCoalesceChanges:_insertSectionChanges]; [_ASHierarchySectionChange sortAndCoalesceChanges:_reloadSectionChanges]; - [_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:_deletedSections]; - [_ASHierarchyItemChange sortAndCoalesceChanges:_reloadItemChanges ignoringChangesInSections:_reloadedSections]; - [_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:_insertedSections]; + + _deletedSections = [[_ASHierarchySectionChange allIndexesInChanges:_deleteSectionChanges] copy]; + _insertedSections = [[_ASHierarchySectionChange allIndexesInChanges:_insertSectionChanges] copy]; + _reloadedSections = [[_ASHierarchySectionChange allIndexesInChanges:_reloadSectionChanges] copy]; + + // These are invalid old section indexes. + NSMutableIndexSet *deletedOrReloaded = [_deletedSections mutableCopy]; + [deletedOrReloaded addIndexes:_reloadedSections]; + + // These are invalid new section indexes. + NSMutableIndexSet *insertedOrReloaded = [_insertedSections mutableCopy]; + + // Get the new section that each reloaded section index corresponds to. + [_reloadedSections enumerateIndexesUsingBlock:^(NSUInteger oldIndex, __unused BOOL * stop) { + NSUInteger newIndex = [self newSectionForOldSection:oldIndex]; + if (newIndex != NSNotFound) { + [insertedOrReloaded addIndex:newIndex]; + } + }]; + + // Ignore item reloads/deletes in reloaded/deleted sections. + [_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:deletedOrReloaded]; + [_ASHierarchyItemChange sortAndCoalesceChanges:_reloadItemChanges ignoringChangesInSections:deletedOrReloaded]; + + // Ignore item inserts in reloaded(new)/inserted sections. + [_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:insertedOrReloaded]; } } @@ -218,38 +241,46 @@ + (void)sortAndCoalesceChanges:(NSMutableArray *)changes NSMutableArray *result = [NSMutableArray new]; __block ASDataControllerAnimationOptions currentOptions = 0; - __block NSMutableIndexSet *currentIndexes = nil; - NSUInteger lastIndex = allIndexes.lastIndex; - + NSMutableIndexSet *currentIndexes = [NSMutableIndexSet indexSet]; + NSEnumerationOptions options = type == _ASHierarchyChangeTypeDelete ? NSEnumerationReverse : kNilOptions; + [allIndexes enumerateIndexesWithOptions:options usingBlock:^(NSUInteger idx, __unused BOOL * stop) { ASDataControllerAnimationOptions options = [animationOptions[@(idx)] integerValue]; - BOOL endingCurrentGroup = NO; - - if (currentIndexes == nil) { - // Starting a new group - currentIndexes = [NSMutableIndexSet indexSetWithIndex:idx]; - currentOptions = options; - } else if (options == currentOptions) { - // Continuing the current group - [currentIndexes addIndex:idx]; - } else { - endingCurrentGroup = YES; - } - - BOOL endingLastGroup = (currentIndexes != nil && lastIndex == idx); - - if (endingCurrentGroup || endingLastGroup) { - _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:currentIndexes animationOptions:currentOptions]; + + // 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]; - currentOptions = 0; - currentIndexes = nil; + [currentIndexes removeAllIndexes]; } + + // Start a new group if needed. + if (currentIndexes.count == 0) { + currentOptions = options; + } + + [currentIndexes addIndex:idx]; }]; - + + // 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 *)allIndexesInChanges:(NSArray *)changes +{ + NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; + for (_ASHierarchySectionChange *change in changes) { + [indexes addIndexes:change.indexSet]; + } + return indexes; +} + @end @implementation _ASHierarchyItemChange @@ -301,36 +332,34 @@ + (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSectio // Create new changes by grouping sorted changes by animation option NSMutableArray *result = [NSMutableArray new]; - + ASDataControllerAnimationOptions currentOptions = 0; - NSMutableArray *currentIndexPaths = nil; - NSIndexPath *lastIndexPath = allIndexPaths.lastObject; - + NSMutableArray *currentIndexPaths = [NSMutableArray array]; + for (NSIndexPath *indexPath in allIndexPaths) { ASDataControllerAnimationOptions options = [animationOptions[indexPath] integerValue]; - BOOL endingCurrentGroup = NO; - - if (currentIndexPaths == nil) { - // Starting a new group - currentIndexPaths = [NSMutableArray arrayWithObject:indexPath]; - currentOptions = options; - } else if (options == currentOptions) { - // Continuing the current group - [currentIndexPaths addObject:indexPath]; - } else { - endingCurrentGroup = YES; - } - - BOOL endingLastGroup = (currentIndexPaths != nil && (NSOrderedSame == [lastIndexPath compare:indexPath])); - if (endingCurrentGroup || endingLastGroup) { - _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:currentIndexPaths animationOptions:currentOptions presorted:YES]; + // 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]; - currentOptions = 0; - currentIndexPaths = nil; + [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]; }