diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index a2d5e2ec44..9b7fe864b5 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -407,8 +407,9 @@ NS_ASSUME_NONNULL_BEGIN * due to the data access in async mode. * * @param collectionView The sender. + * @deprecated The data source is always accessed on the main thread, and this method will not be called. */ -- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView; +- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED; /** * Indicator to unlock the data source for data fetching in async mode. @@ -416,8 +417,9 @@ NS_ASSUME_NONNULL_BEGIN * due to the data access in async mode. * * @param collectionView The sender. + * @deprecated The data source is always accessed on the main thread, and this method will not be called. */ -- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView; +- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED; @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 0c979f4320..68031f464a 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -112,7 +112,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; BOOL _performingBatchUpdates; NSMutableArray *_batchUpdateBlocks; - BOOL _asyncDataFetchingEnabled; _ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only BOOL _isDeallocating; @@ -155,14 +154,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; unsigned int asyncDataSourceNodeForItemAtIndexPath:1; unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1; unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1; - unsigned int asyncDataSourceCollectionViewLockDataSource:1; - unsigned int asyncDataSourceCollectionViewUnlockDataSource:1; unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1; } _asyncDataSourceFlags; } -@property (atomic, assign) BOOL asyncDataSourceLocked; - // Used only when ASCollectionView is created directly rather than through ASCollectionNode. // We create a node so that logic related to appearance, memory management, etc can be located there // for both the node-based and view-based version of the table. @@ -231,7 +226,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _rangeController.delegate = self; _rangeController.layoutController = _layoutController; - _dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:NO]; + _dataController = [[ASCollectionDataController alloc] init]; _dataController.delegate = _rangeController; _dataController.dataSource = self; _dataController.environmentDelegate = self; @@ -240,9 +235,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _leadingScreensForBatching = 2.0; - _asyncDataFetchingEnabled = NO; - _asyncDataSourceLocked = NO; - _performingBatchUpdates = NO; _batchUpdateBlocks = [NSMutableArray array]; @@ -375,9 +367,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; - _asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]; - _asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)]; - _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];; + _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath); @@ -937,26 +927,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)dataControllerLockDataSource -{ - ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked"); - - self.asyncDataSourceLocked = YES; - if (_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource) { - [_asyncDataSource collectionViewLockDataSource:self]; - } -} - -- (void)dataControllerUnlockDataSource -{ - ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked"); - - self.asyncDataSourceLocked = NO; - if (_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource) { - [_asyncDataSource collectionViewUnlockDataSource:self]; - } -} - - (id)dataControllerEnvironment { if (self.collectionNode) { diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index 4ab4877121..7d36cc3d4d 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -44,18 +44,8 @@ NS_ASSUME_NONNULL_BEGIN * The frame of the table view changes as table cells are added and deleted. * * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - * - * @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op. - * - * @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and - * `tableView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically - * from calling thread. - * Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for - * large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically, - * we will lock the data source through `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` after the data fetching. - * The application should not update the data source while the data source is locked, to keep data consistence. */ -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled; +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style; /** * Tuning parameters for a range type in full mode. @@ -363,8 +353,9 @@ NS_ASSUME_NONNULL_BEGIN * due to the data access in async mode. * * @param tableView The sender. + * @deprecated The data source is always accessed on the main thread, and this method will not be called. */ -- (void)tableViewLockDataSource:(ASTableView *)tableView; +- (void)tableViewLockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED; /** * Indicator to unlock the data source for data fetching in asyn mode. @@ -372,8 +363,9 @@ NS_ASSUME_NONNULL_BEGIN * due to the data access in async mode. * * @param tableView The sender. + * @deprecated The data source is always accessed on the main thread, and this method will not be called. */ -- (void)tableViewUnlockDataSource:(ASTableView *)tableView; +- (void)tableViewUnlockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED; @end @@ -458,4 +450,10 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASTableViewDelegate @end +@interface ASTableView (Deprecated) + +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled ASDISPLAYNODE_DEPRECATED; + +@end + NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 871589b49d..c978bb9e5e 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -100,8 +100,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ASRangeController *_rangeController; - BOOL _asyncDataFetchingEnabled; - ASBatchContext *_batchContext; NSIndexPath *_pendingVisibleIndexPath; @@ -133,12 +131,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; unsigned int asyncDataSourceNumberOfSectionsInTableView:1; unsigned int asyncDataSourceTableViewNodeBlockForRowAtIndexPath:1; unsigned int asyncDataSourceTableViewNodeForRowAtIndexPath:1; - unsigned int asyncDataSourceTableViewLockDataSource:1; - unsigned int asyncDataSourceTableViewUnlockDataSource:1; } _asyncDataSourceFlags; } -@property (atomic, assign) BOOL asyncDataSourceLocked; @property (nonatomic, strong, readwrite) ASDataController *dataController; // Used only when ASTableView is created directly rather than through ASTableNode. @@ -177,16 +172,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _rangeController.dataSource = self; _rangeController.delegate = self; - _dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:NO]; + _dataController = [[dataControllerClass alloc] init]; _dataController.dataSource = self; _dataController.delegate = _rangeController; _dataController.environmentDelegate = self; _layoutController.dataSource = _dataController; - _asyncDataFetchingEnabled = NO; - _asyncDataSourceLocked = NO; - _leadingScreensForBatching = 2.0; _batchContext = [[ASBatchContext alloc] init]; @@ -290,8 +282,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]; _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; - _asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)]; - _asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)]; // Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath: ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath || _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath); @@ -1083,28 +1073,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; CGSizeMake(_nodesConstrainedWidth, FLT_MAX)); } -- (void)dataControllerLockDataSource -{ - ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked"); - - self.asyncDataSourceLocked = YES; - - if (_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource) { - [_asyncDataSource tableViewLockDataSource:self]; - } -} - -- (void)dataControllerUnlockDataSource -{ - ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked"); - - self.asyncDataSourceLocked = NO; - - if (_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource) { - [_asyncDataSource tableViewUnlockDataSource:self]; - } -} - - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section { return [_asyncDataSource tableView:self numberOfRowsInSection:section]; diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.m index 21fe2b8842..efce00ad31 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.m @@ -26,17 +26,6 @@ @implementation ASChangeSetDataController -- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled -{ - if (!(self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled])) { - return nil; - } - - _changeSetBatchUpdateCounter = 0; - - return self; -} - #pragma mark - Batching (External API) - (void)beginUpdates @@ -66,6 +55,8 @@ [super deleteSections:change.indexSet withAnimationOptions:change.animationOptions]; } + // TODO: Shouldn't reloads be processed before deletes, since deletes affect + // the index space and reloads don't? for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { [super reloadSections:change.indexSet withAnimationOptions:change.animationOptions]; } diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index c29fef148f..b54583c2a2 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -32,9 +32,9 @@ NSMutableDictionary *> *_pendingContexts; } -- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled +- (instancetype)init { - self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled]; + self = [super init]; if (self != nil) { _pendingContexts = [NSMutableDictionary dictionary]; } @@ -53,15 +53,13 @@ - (void)willReloadData { - NSArray *keys = _pendingContexts.allKeys; - for (NSString *kind in keys) { - NSMutableArray *contexts = _pendingContexts[kind]; + [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, __unused BOOL * _Nonnull stop) { // Remove everything that existed before the reload, now that we're ready to insert replacements NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; NSArray *editingNodes = [self editingNodesOfKind:kind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; + NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; // Insert each section @@ -75,8 +73,8 @@ [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; - [_pendingContexts removeObjectForKey:kind]; - } + }]; + [_pendingContexts removeAllObjects]; } - (void)prepareForInsertSections:(NSIndexSet *)sections diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 74ed8f0edb..a740d4f1ce 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -57,17 +57,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController; -/** - Lock the data source for data fetching. - */ -- (void)dataControllerLockDataSource; - -/** - Unlock the data source after data fetching. - */ -- (void)dataControllerUnlockDataSource; - - @end @protocol ASDataControllerEnvironmentDelegate @@ -135,20 +124,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ @property (nonatomic, weak) id environmentDelegate; -/** - * Designated initializer. - * - * @param asyncDataFetchingEnabled Enable the data fetching in async mode. - * - * @discussion If enabled, we will fetch data through `dataController:nodeAtIndexPath:` and `dataController:rowsInSection:` in background thread. - * Otherwise, the methods will be invoked synchronically in calling thread. Enabling data fetching in async mode could avoid blocking main thread - * while allocating cell on main thread, which is frequently reported issue for handling large scale data. On another hand, the application code - * will take the responsibility to avoid data inconsistency. Specifically, we will lock the data source through `dataControllerLockDataSource`, - * and unlock it by `dataControllerUnlockDataSource` after the data fetching. The application should not update the data source while - * the data source is locked. - */ -- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; - /** @name Data Updating */ - (void)beginUpdates; diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 296f67b81d..86967fc99a 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -44,8 +44,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking. NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. - BOOL _asyncDataFetchingEnabled; - BOOL _initialReloadDataHasBeenCalled; BOOL _delegateDidInsertNodes; @@ -62,7 +60,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Lifecycle -- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled +- (instancetype)init { if (!(self = [super init])) { return nil; @@ -83,7 +81,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; _editingTransactionQueue.name = @"org.AsyncDisplayKit.ASDataController.editingTransactionQueue"; _batchUpdateCounter = 0; - _asyncDataFetchingEnabled = asyncDataFetchingEnabled; return self; } @@ -119,6 +116,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)batchLayoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock { + ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd)); + NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; NSUInteger count = contexts.count; @@ -143,8 +142,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize { - [node measureWithSizeRange:constrainedSize]; - node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); + CGSize size = [node measureWithSizeRange:constrainedSize].size; + node.frame = { .size = size }; } /** @@ -152,6 +151,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_batchLayoutNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd)); + [self batchLayoutNodesFromContexts:contexts ofKind:ASDataControllerRowNodeKind completion:^(NSArray *nodes, NSArray *indexPaths) { // Insert finished nodes into data storage [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; @@ -163,6 +164,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_layoutNodes:(NSArray *)nodes fromContexts:(NSArray *)contexts atIndexesOfRange:(NSRange)range ofKind:(NSString *)kind { + ASDisplayNodeAssert([NSOperationQueue currentQueue] != _editingTransactionQueue, @"%@ should not be called on the editing transaction queue", NSStringFromSelector(_cmd)); + if (_dataSource == nil) { return; } @@ -182,6 +185,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)_layoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock { + ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd)); + if (!contexts.count || _dataSource == nil) { return; } @@ -278,7 +283,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableArray *editingNodes = _editingNodes[kind]; ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); - _editingNodes[kind] = editingNodes; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. NSMutableArray *completedNodes = ASTwoDimensionalArrayDeepMutableCopy(editingNodes); @@ -359,7 +363,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd)); + [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + ASDisplayNodeAssertMainThread(); + if (_delegateDidInsertNodes) [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; @@ -373,7 +381,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd)); + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + ASDisplayNodeAssertMainThread(); + if (_delegateDidDeleteNodes) [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; @@ -387,7 +399,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd)); + [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) { + ASDisplayNodeAssertMainThread(); + if (_delegateDidInsertSections) [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; }]; @@ -401,7 +417,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; */ - (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd)); + [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) { + ASDisplayNodeAssertMainThread(); + if (_delegateDidDeleteSections) [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }]; @@ -426,49 +446,44 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; ASDisplayNodeAssertMainThread(); [_editingTransactionQueue waitUntilAllOperationsAreFinished]; - [self accessDataSourceSynchronously:synchronously withBlock:^{ - NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; + NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; + NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; + NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; - // Allow subclasses to perform setup before going into the edit transaction - [self prepareForReloadData]; + // Allow subclasses to perform setup before going into the edit transaction + [self prepareForReloadData]; + + [_editingTransactionQueue addOperationWithBlock:^{ + LOG(@"Edit Transaction - reloadData"); - void (^transactionBlock)() = ^{ - LOG(@"Edit Transaction - reloadData"); - - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; - 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]; - - // Insert empty sections - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; - for (int i = 0; i < sectionCount; i++) { - [sections addObject:[[NSMutableArray alloc] init]]; - } - [self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions]; - - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; - - if (completion) { - dispatch_async(dispatch_get_main_queue(), completion); - } - }; + // Remove everything that existed before the reload, now that we're ready to insert replacements + NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; + NSUInteger editingNodesSectionCount = editingNodes.count; - if (synchronously) { - transactionBlock(); - } else { - [_editingTransactionQueue addOperationWithBlock:transactionBlock]; + if (editingNodesSectionCount) { + NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)]; + [self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions]; + [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; + } + + [self willReloadData]; + + // Insert empty sections + NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; + for (int i = 0; i < sectionCount; i++) { + [sections addObject:[[NSMutableArray alloc] init]]; + } + [self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions]; + + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; + + if (completion) { + dispatch_async(dispatch_get_main_queue(), completion); } }]; + if (synchronously) { + [self waitUntilAllUpdatesAreCommitted]; + } }]; } @@ -491,45 +506,21 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Data Source Access (Calling _dataSource) -/** - * Safely locks access to the data source and executes the given block, unlocking once complete. - * - * @discussion When `asyncDataFetching` is enabled, the block is executed on a background thread. - */ -- (void)accessDataSourceWithBlock:(dispatch_block_t)block -{ - [self accessDataSourceSynchronously:NO withBlock:block]; -} - -- (void)accessDataSourceSynchronously:(BOOL)synchronously withBlock:(dispatch_block_t)block -{ - if (!synchronously && _asyncDataFetchingEnabled) { - [_dataSource dataControllerLockDataSource]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - block(); - [_dataSource dataControllerUnlockDataSource]; - }); - } else { - [_dataSource dataControllerLockDataSource]; - block(); - [_dataSource dataControllerUnlockDataSource]; - } -} - /** * Fetches row contexts for the provided sections from the data source. */ - (NSArray *)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet { + ASDisplayNodeAssertMainThread(); + id environment = [self.environmentDelegate dataControllerEnvironment]; ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; NSMutableArray *contexts = [NSMutableArray array]; - [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx]; - NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; + [indexSet enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL *stop) { + NSUInteger rowNum = [_dataSource dataController:self rowsInSection:sectionIndex]; for (NSUInteger i = 0; i < rowNum; i++) { - NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex]; ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; @@ -628,24 +619,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"Edit Command - insertSections: %@", sections); [_editingTransactionQueue waitUntilAllOperationsAreFinished]; - [self accessDataSourceWithBlock:^{ - NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections]; + NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections]; - [self prepareForInsertSections:sections]; + [self prepareForInsertSections:sections]; + + [_editingTransactionQueue addOperationWithBlock:^{ + [self willInsertSections:sections]; + + LOG(@"Edit Transaction - insertSections: %@", sections); + NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; + for (NSUInteger i = 0; i < sections.count; i++) { + [sectionArray addObject:[NSMutableArray array]]; + } + + [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - [_editingTransactionQueue addOperationWithBlock:^{ - [self willInsertSections:sections]; - - LOG(@"Edit Transaction - insertSections: %@", sections); - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }]; + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; } @@ -678,23 +667,21 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; - [self accessDataSourceWithBlock:^{ - NSArray *contexts= [self _populateFromDataSourceWithSectionIndexSet:sections]; + NSArray *contexts= [self _populateFromDataSourceWithSectionIndexSet:sections]; - [self prepareForReloadSections:sections]; + [self prepareForReloadSections:sections]; + + [_editingTransactionQueue addOperationWithBlock:^{ + [self willReloadSections:sections]; + + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - [_editingTransactionQueue addOperationWithBlock:^{ - [self willReloadSections:sections]; + LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForTwoDimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); + + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForTwoDimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - // reinsert the elements - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }]; + // reinsert the elements + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; } @@ -818,27 +805,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - [self accessDataSourceWithBlock:^{ - id environment = [self.environmentDelegate dataControllerEnvironment]; - ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; - - for (NSIndexPath *indexPath in sortedIndexPaths) { - ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; - [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock - indexPath:indexPath - constrainedSize:constrainedSize - environmentTraitCollection:environmentTraitCollection]]; - } + id environment = [self.environmentDelegate dataControllerEnvironment]; + ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; + + for (NSIndexPath *indexPath in sortedIndexPaths) { + ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; + [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock + indexPath:indexPath + constrainedSize:constrainedSize + environmentTraitCollection:environmentTraitCollection]]; + } - [self prepareForInsertRowsAtIndexPaths:indexPaths]; + [self prepareForInsertRowsAtIndexPaths:indexPaths]; - [_editingTransactionQueue addOperationWithBlock:^{ - [self willInsertRowsAtIndexPaths:indexPaths]; + [_editingTransactionQueue addOperationWithBlock:^{ + [self willInsertRowsAtIndexPaths:indexPaths]; - LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }]; + LOG(@"Edit Transaction - insertRows: %@", indexPaths); + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; } @@ -874,35 +859,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; - // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. - [self accessDataSourceWithBlock:^{ - NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - - // Sort indexPath to avoid messing up the index when deleting - // FIXME: Shouldn't deletes be sorted in descending order? - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - - id environment = [self.environmentDelegate dataControllerEnvironment]; - ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; - - for (NSIndexPath *indexPath in sortedIndexPaths) { - ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; - [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock - indexPath:indexPath - constrainedSize:constrainedSize - environmentTraitCollection:environmentTraitCollection]]; - } + NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + + // Sort indexPath to avoid messing up the index when deleting + // FIXME: Shouldn't deletes be sorted in descending order? + NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; + + id environment = [self.environmentDelegate dataControllerEnvironment]; + ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; + + for (NSIndexPath *indexPath in sortedIndexPaths) { + ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; + [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock + indexPath:indexPath + constrainedSize:constrainedSize + environmentTraitCollection:environmentTraitCollection]]; + } - [self prepareForReloadRowsAtIndexPaths:indexPaths]; - - [_editingTransactionQueue addOperationWithBlock:^{ - [self willReloadRowsAtIndexPaths:indexPaths]; + [self prepareForReloadRowsAtIndexPaths:indexPaths]; + + [_editingTransactionQueue addOperationWithBlock:^{ + [self willReloadRowsAtIndexPaths:indexPaths]; - LOG(@"Edit Transaction - reloadRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }]; + LOG(@"Edit Transaction - reloadRows: %@", indexPaths); + [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; }]; }]; } @@ -935,16 +917,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return; } - [self accessDataSourceWithBlock:^{ - [nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) { - [section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - ASLayout *layout = [node measureWithSizeRange:constrainedSize]; - node.frame = CGRectMake(0.0f, 0.0f, layout.size.width, layout.size.height); - }]; - }]; - }]; + NSUInteger sectionIndex = 0; + for (NSMutableArray *section in nodes) { + NSUInteger rowIndex = 0; + for (ASCellNode *node in section) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + CGSize size = [node measureWithSizeRange:constrainedSize].size; + node.frame = { .size = size }; + rowIndex += 1; + } + sectionIndex += 1; + } } - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions @@ -1021,17 +1005,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; { ASDisplayNodeAssertMainThread(); - - NSArray *nodes = [self completedNodes]; - NSUInteger numberOfNodes = nodes.count; + NSInteger section = 0; // Loop through each section to look for the cellNode - for (NSUInteger i = 0; i < numberOfNodes; i++) { - NSArray *sectionNodes = nodes[i]; - NSUInteger cellIndex = [sectionNodes indexOfObjectIdenticalTo:cellNode]; - if (cellIndex != NSNotFound) { - return [NSIndexPath indexPathForRow:cellIndex inSection:i]; + for (NSArray *sectionNodes in [self completedNodes]) { + NSUInteger item = [sectionNodes indexOfObjectIdenticalTo:cellNode]; + if (item != NSNotFound) { + return [NSIndexPath indexPathForItem:item inSection:section]; } + section += 1; } return nil; diff --git a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm index 5c7e437387..370f533980 100644 --- a/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm +++ b/AsyncDisplayKit/Private/ASMultidimensionalArrayUtils.mm @@ -11,6 +11,10 @@ #import "ASAssert.h" #import "ASMultidimensionalArrayUtils.h" +// Import UIKit to get [NSIndexPath indexPathForItem:inSection:] which uses +// static memory addresses rather than allocating new index path objects. +#import + #pragma mark - Internal Methods static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, @@ -25,8 +29,10 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray } if (curIndexPath.length < dimension - 1) { - for (int i = 0; i < mutableArray.count; i++) { - ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray[i], indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock); + NSInteger i = 0; + for (NSMutableArray *subarray in mutableArray) { + ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(subarray, indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock); + i += 1; } } else { NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init]; @@ -72,7 +78,12 @@ static BOOL ASElementExistsAtIndexPathForMultidimensionalArray(NSArray *array, N NSUInteger indexesLength = indexLength - 1; NSUInteger indexes[indexesLength]; [indexPath getIndexes:indexes range:NSMakeRange(1, indexesLength)]; - NSIndexPath *indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength]; + NSIndexPath *indexPathByRemovingFirstIndex; + if (indexesLength == 2) { + indexPathByRemovingFirstIndex = [NSIndexPath indexPathForItem:indexes[1] inSection:indexes[0]]; + } else { + indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength]; + } return ASElementExistsAtIndexPathForMultidimensionalArray(array[firstIndex], indexPathByRemovingFirstIndex); } @@ -184,9 +195,8 @@ NSArray *ASIndexPathsForTwoDimensionalArray(NSArray * twoDimensionalA NSUInteger section = 0; for (NSArray *subarray in twoDimensionalArray) { ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - NSUInteger itemCount = subarray.count; - for (NSUInteger item = 0; item < itemCount; item++) { - [result addObject:[NSIndexPath indexPathWithIndexes:(const NSUInteger []){ section, item } length:2]]; + for (NSUInteger item = 0; item < subarray.count; item++) { + [result addObject:[NSIndexPath indexPathForItem:item inSection:section]]; } section++; } diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 19a827ea28..7cb13fb8ca 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -408,8 +408,7 @@ { CGSize tableViewSize = CGSizeMake(100, 500); ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) - style:UITableViewStylePlain - asyncDataFetching:YES]; + style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; tableView.asyncDelegate = dataSource;