From cc7ca4a08fe2f43b06a8c4b74344679a4277d272 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 5 Mar 2016 20:22:18 -0800 Subject: [PATCH] [ASDataController] If app code issues edit commands before first reload, ignore them, as UIKit will call -reloadData. --- AsyncDisplayKit/Details/ASDataController.mm | 53 +++++++------------ AsyncDisplayKit/Details/ASMainSerialQueue.mm | 5 ++ .../Private/ASMultidimensionalArrayUtils.h | 7 ++- .../Private/ASMultidimensionalArrayUtils.mm | 48 ++++++++++++----- 4 files changed, 66 insertions(+), 47 deletions(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 8c48e81732..7e5f77b8cc 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -42,6 +42,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. BOOL _asyncDataFetchingEnabled; + + BOOL _initialReloadDataHasBeenCalled; BOOL _delegateDidInsertNodes; BOOL _delegateDidDeleteNodes; @@ -267,7 +269,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return; } - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); + LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForTwoDimensionalArray(_editingNodes[kind])); NSMutableArray *editingNodes = _editingNodes[kind]; ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; @@ -376,32 +378,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Initial Load & Full Reload (External API) -- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - [self accessDataSourceWithBlock:^{ - NSMutableArray *indexPaths = [NSMutableArray array]; - NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; - - // insert sections - [self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0]; - - for (NSUInteger i = 0; i < sectionNum; i++) { - NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i]; - - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; - for (NSUInteger j = 0; j < rowNum; j++) { - [indexPaths addObject:[indexPath indexPathByAddingIndex:j]]; - } - } - - // insert elements - [self insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; - }]; -} - - (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion { [self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion]; @@ -414,6 +390,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion { + _initialReloadDataHasBeenCalled = YES; [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); [_editingTransactionQueue waitUntilAllOperationsAreFinished]; @@ -430,12 +407,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"Edit Transaction - reloadData"); // Remove everything that existed before the reload, now that we're ready to insert replacements - NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; - [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + NSUInteger editingNodesSectionCount = editingNodes.count; + + if (editingNodesSectionCount) { + NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)]; + [self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions]; + [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + } [self willReloadData]; @@ -591,6 +570,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; { // This method needs to block the thread and synchronously perform the operation if we are not // queuing commands for begin/endUpdates. If we are queuing, it needs to return immediately. + if (!_initialReloadDataHasBeenCalled) { + return; + } + + // If we have never performed a reload, there is no value in executing edit operations as the initial + // reload will directly re-query the latest state of the datasource - so completely skip the block in this case. if (_batchUpdateCounter == 0) { block(); } else { @@ -667,7 +652,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); + LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForTwoDimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; @@ -900,7 +885,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind { - return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil; + return _editingNodes[kind] != nil ? ASIndexPathsForTwoDimensionalArray(_editingNodes[kind]) : nil; } - (NSMutableArray *)editingNodesOfKind:(NSString *)kind diff --git a/AsyncDisplayKit/Details/ASMainSerialQueue.mm b/AsyncDisplayKit/Details/ASMainSerialQueue.mm index c15e3623b7..209d42beb5 100644 --- a/AsyncDisplayKit/Details/ASMainSerialQueue.mm +++ b/AsyncDisplayKit/Details/ASMainSerialQueue.mm @@ -65,4 +65,9 @@ } } +- (NSString *)description +{ + return [[super description] stringByAppendingFormat:@" Blocks: %@", _blocks]; +} + @end diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h index 432df556e6..756b1cc775 100644 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h +++ b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.h @@ -50,7 +50,12 @@ extern NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray extern NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *MultidimensionalArray, NSIndexSet *indexSet); /** - * Return all the index paths of mutable multidimensional array, in ascending order. + * Return all the index paths of a two-dimensional array, in ascending order. + */ +extern NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalArray); + +/** + * Return all the index paths of a multidimensional array, in ascending order. */ extern NSArray *ASIndexPathsForMultidimensionalArray(NSArray *MultidimensionalArray); diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm index 2f40e9d21a..579544d02f 100644 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm +++ b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm @@ -16,7 +16,8 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray NSUInteger &curIdx, NSIndexPath *curIndexPath, const NSUInteger dimension, - void (^updateBlock)(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx)) { + void (^updateBlock)(NSMutableArray *arr, NSIndexSet *indexSet, NSUInteger idx)) +{ if (curIdx == indexPaths.count) { return; } @@ -38,7 +39,8 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray } } -static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, NSIndexPath *curIndexPath, NSMutableArray *res) { +static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, NSIndexPath *curIndexPath, NSMutableArray *res) +{ if (![obj isKindOfClass:[NSArray class]]) { [res addObject:curIndexPath]; } else { @@ -51,7 +53,8 @@ static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, N #pragma mark - Public Methods -NSObject *ASMultidimensionalArrayDeepMutableCopy(NSObject *obj) { +NSObject *ASMultidimensionalArrayDeepMutableCopy(NSObject *obj) +{ if ([obj isKindOfClass:[NSArray class]]) { NSArray *arr = (NSArray *)obj; NSMutableArray * mutableArr = [NSMutableArray arrayWithCapacity:arr.count]; @@ -64,15 +67,18 @@ NSObject *ASMultidimensionalArrayDeepMutableCopy(NSObject return obj; } -NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) { +NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) +{ NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; for (NSArray *subarray in array) { + ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); [newArray addObject:[subarray mutableCopy]]; } return newArray; } -void ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths, NSArray *elements) { +void ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths, NSArray *elements) +{ ASDisplayNodeCAssert(indexPaths.count == elements.count, @"Inconsistent indexPaths and elements"); if (!indexPaths.count) { @@ -89,7 +95,8 @@ void ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(NSMutableArray *mutab ASDisplayNodeCAssert(curIdx == indexPaths.count, @"Indexpath is out of range !"); } -void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) { +void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) +{ if (!indexPaths.count) { return; } @@ -104,7 +111,8 @@ void ASDeleteElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutable ASDisplayNodeCAssert(curIdx == indexPaths.count, @"Indexpath is out of range !"); } -NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) { +NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) +{ NSUInteger curIdx = 0; NSIndexPath *indexPath = [[NSIndexPath alloc] init]; NSMutableArray *deletedElements = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; @@ -122,8 +130,9 @@ NSArray *ASFindElementsInMultidimensionalArrayAtIndexPaths(NSMutableArray *mutab return deletedElements; } -NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet) { - NSMutableArray *res = [[NSMutableArray alloc] initWithCapacity:multidimensionalArray.count]; +NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensionalArray, NSIndexSet *indexSet) +{ + NSMutableArray *res = [NSMutableArray array]; [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { ASRecursivelyFindIndexPathsForMultidimensionalArray(multidimensionalArray[idx], [NSIndexPath indexPathWithIndex:idx], res); }]; @@ -131,9 +140,24 @@ NSArray *ASIndexPathsForMultidimensionalArrayAtIndexSet(NSArray *multidimensiona return res; } -NSArray *ASIndexPathsForMultidimensionalArray(NSArray *multidimensionalArray) { - NSMutableArray *res = [NSMutableArray arrayWithCapacity:multidimensionalArray.count]; +NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalArray) +{ + NSMutableArray *result = [NSMutableArray array]; + NSUInteger section = 0; + for (NSArray *subarray in twoDimensionalArray) { + ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); + NSUInteger itemCount = subarray.count; + for (int item = 0; item < itemCount; item++) { + [result addObject:[NSIndexPath indexPathForItem:item inSection:section]]; + } + section++; + } + return result; +} + +NSArray *ASIndexPathsForMultidimensionalArray(NSArray *multidimensionalArray) +{ + NSMutableArray *res = [NSMutableArray array]; ASRecursivelyFindIndexPathsForMultidimensionalArray(multidimensionalArray, [[NSIndexPath alloc] init], res); return res; } -