diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 161e4a20aa..5e71ffd40c 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -87,7 +87,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { +@interface ASCollectionView () { ASCollectionViewProxy *_proxyDataSource; ASCollectionViewProxy *_proxyDelegate; @@ -232,9 +232,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _rangeController.delegate = self; _rangeController.layoutController = _layoutController; - _dataController = [[ASCollectionDataController alloc] init]; + _dataController = [[ASCollectionDataController alloc] initWithDataSource:self]; _dataController.delegate = _rangeController; - _dataController.dataSource = self; _dataController.environmentDelegate = self; _batchContext = [[ASBatchContext alloc] init]; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 4b0be991ac..958530cca9 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -189,8 +189,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _rangeController.dataSource = self; _rangeController.delegate = self; - _dataController = [[dataControllerClass alloc] init]; - _dataController.dataSource = self; + _dataController = [[dataControllerClass alloc] initWithDataSource:self]; _dataController.delegate = _rangeController; _dataController.environmentDelegate = self; diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index c1534ea827..2f19e09757 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -40,6 +40,8 @@ @interface ASCollectionDataController : ASChangeSetDataController +- (instancetype)initWithDataSource:(id)dataSource NS_DESIGNATED_INITIALIZER; + - (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; @end \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index f3bcede28c..8f5d505815 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -31,11 +31,14 @@ NSMutableDictionary *> *_pendingContexts; } -- (instancetype)init +- (instancetype)initWithDataSource:(id)dataSource { - self = [super init]; + self = [super initWithDataSource:dataSource]; if (self != nil) { _pendingContexts = [NSMutableDictionary dictionary]; + _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; + + ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [dataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]); } return self; } @@ -69,7 +72,7 @@ } [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; - [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; }]; @@ -95,7 +98,7 @@ } [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; }]; @@ -145,7 +148,7 @@ - (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths { [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { - [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; }]; @@ -178,7 +181,7 @@ } } - [self batchLayoutNodesFromContexts:reinsertedContexts ofKind:kind completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; } @@ -240,20 +243,20 @@ - (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray *)contexts environmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection { - ASCellNodeBlock supplementaryCellBlock; - if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { - supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; - } else { - ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; - supplementaryCellBlock = ^{ return supplementaryNode; }; - } - - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock - indexPath:indexPath - constrainedSize:constrainedSize - environmentTraitCollection:environmentTraitCollection]; - [contexts addObject:context]; + ASCellNodeBlock supplementaryCellBlock; + if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { + supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; + } else { + ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; + supplementaryCellBlock = ^{ return supplementaryNode; }; + } + + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; + ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock + indexPath:indexPath + constrainedSize:constrainedSize + environmentTraitCollection:environmentTraitCollection]; + [contexts addObject:context]; } #pragma mark - Sizing query @@ -296,12 +299,4 @@ return (id)self.dataSource; } -- (void)setDataSource:(id)dataSource -{ - [super setDataSource:dataSource]; - _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; - - ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]); -} - @end diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 2c9c324425..7d748663dd 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -109,10 +109,12 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; @protocol ASFlowLayoutControllerDataSource; @interface ASDataController : ASDealloc2MainObject +- (instancetype)initWithDataSource:(id)dataSource NS_DESIGNATED_INITIALIZER; + /** Data source for fetching data info. */ -@property (nonatomic, weak) id dataSource; +@property (nonatomic, weak, readonly) id dataSource; /** Delegate to notify when data is updated. diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 21e56e3dc4..f738b55e01 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -23,6 +23,9 @@ //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) +#define AS_MEASURE_AVOIDED_DATACONTROLLER_WORK 0 + +#define RETURN_IF_NO_DATASOURCE(val) if (_dataSource == nil) { return val; } #define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd)) const static NSUInteger kASDataControllerSizingCountPerProcessor = 5; @@ -31,6 +34,13 @@ const static char * kASDataControllerEditingQueueContext = "kASDataControllerEdi NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK +@interface ASDataController (AvoidedWorkMeasuring) ++ (void)_didLayoutNode; ++ (void)_expectToInsertNodes:(NSUInteger)count; +@end +#endif + @interface ASDataController () { NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. @@ -60,13 +70,15 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; #pragma mark - Lifecycle -- (instancetype)init +- (instancetype)initWithDataSource:(id)dataSource { if (!(self = [super init])) { return nil; } ASDisplayNodeAssert(![self isMemberOfClass:[ASDataController class]], @"ASDataController is an abstract class and should not be instantiated. Instantiate a subclass instead."); + _dataSource = dataSource; + _completedNodes = [NSMutableDictionary dictionary]; _editingNodes = [NSMutableDictionary dictionary]; @@ -87,6 +99,13 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; return self; } +- (instancetype)init +{ + ASDisplayNodeFailAssert(@"Failed to call designated initializer."); + id fakeDataSource = nil; + return [self initWithDataSource:fakeDataSource]; +} + - (void)setDelegate:(id)delegate { if (_delegate == delegate) { @@ -116,10 +135,13 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; #pragma mark - Cell Layout -- (void)batchLayoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock +- (void)batchLayoutNodesFromContexts:(NSArray *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler { ASSERT_ON_EDITING_QUEUE; - +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK + [ASDataController _expectToInsertNodes:contexts.count]; +#endif + NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; NSUInteger count = contexts.count; @@ -127,7 +149,9 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; for (NSUInteger i = 0; i < count; i += blockSize) { NSRange batchedRange = NSMakeRange(i, MIN(count - i, blockSize)); NSArray *batchedContexts = [contexts subarrayWithRange:batchedRange]; - [self _layoutNodesFromContexts:batchedContexts ofKind:kind completion:completionBlock]; + NSArray *nodes = [self _layoutNodesFromContexts:batchedContexts]; + NSArray *indexPaths = [ASIndexedNodeContext indexPathsFromContexts:batchedContexts]; + batchCompletionHandler(nodes, indexPaths); } } @@ -144,63 +168,58 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; /** * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. */ -- (void)_batchLayoutNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)_batchLayoutAndInsertNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASSERT_ON_EDITING_QUEUE; - [self batchLayoutNodesFromContexts:contexts ofKind:ASDataControllerRowNodeKind completion:^(NSArray *nodes, NSArray *indexPaths) { + [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { // Insert finished nodes into data storage [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; } -- (void)_layoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock +- (NSArray *)_layoutNodesFromContexts:(NSArray *)contexts { ASSERT_ON_EDITING_QUEUE; - if (!contexts.count || _dataSource == nil) { - return; + NSUInteger nodeCount = contexts.count; + if (!nodeCount || _dataSource == nil) { + return nil; } - NSUInteger nodeCount = contexts.count; - __strong NSIndexPath **allocatedContextIndexPaths = (__strong NSIndexPath **)calloc(nodeCount, sizeof(NSIndexPath *)); __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *)); - for (NSUInteger j = 0; j < nodeCount; j += kASDataControllerSizingCountPerProcessor) { - NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, nodeCount - j); + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_apply(nodeCount, queue, ^(size_t i) { + RETURN_IF_NO_DATASOURCE(); - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_apply(batchCount, queue, ^(size_t i) { - unsigned long k = j + i; - ASIndexedNodeContext *context = contexts[k]; - ASCellNode *node = [context allocateNode]; - if (node == nil) { - ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); - node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. - } - - allocatedContextIndexPaths[k] = context.indexPath; - allocatedNodeBuffer[k] = node; - - [self _layoutNode:node withConstrainedSize:context.constrainedSize]; - }); - } - - // Create nodes and indexPaths array's - NSArray *allocatedNodes = [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount]; - NSArray *indexPaths = [NSArray arrayWithObjects:allocatedContextIndexPaths count:nodeCount]; + // Allocate the node. + ASIndexedNodeContext *context = contexts[i]; + ASCellNode *node = [context allocateNode]; + if (node == nil) { + ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); + node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. + } + + [self _layoutNode:node withConstrainedSize:context.constrainedSize]; +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK + [ASDataController _didLayoutNode]; +#endif + allocatedNodeBuffer[i] = node; + }); + + BOOL canceled = _dataSource == nil; + + // Create nodes array + NSArray *nodes = canceled ? nil : [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount]; // Nil out buffer indexes to allow arc to free the stored cells. for (int i = 0; i < nodeCount; i++) { - allocatedContextIndexPaths[i] = nil; allocatedNodeBuffer[i] = nil; } - free(allocatedContextIndexPaths); free(allocatedNodeBuffer); - if (completionBlock) { - completionBlock(allocatedNodes, indexPaths); - } + return nodes; } - (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath @@ -238,9 +257,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; } LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForTwoDimensionalArray(_editingNodes[kind])); - NSMutableArray *editingNodes = _editingNodes[kind]; - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); - _editingNodes[kind] = editingNodes; + ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); [_mainSerialQueue performBlockOnMainThread:^{ NSMutableArray *allNodes = _completedNodes[kind]; @@ -383,13 +400,10 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; ASDisplayNodeAssertMainThread(); dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; + [self invalidateDataSourceItemCounts]; + NSUInteger sectionCount = [self itemCountsFromDataSource].size(); NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; - - [self invalidateDataSourceItemCounts]; - // Fetch the new item counts upfront. - [self itemCountsFromDataSource]; // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; @@ -416,10 +430,10 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; } [self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions]; - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; + [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; if (completion) { - dispatch_async(dispatch_get_main_queue(), completion); + [_mainSerialQueue performBlockOnMainThread:completion]; } }); if (synchronously) { @@ -455,10 +469,11 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; id environment = [self.environmentDelegate dataControllerEnvironment]; ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; + std::vector counts = [self itemCountsFromDataSource]; NSMutableArray *contexts = [NSMutableArray array]; [indexSet enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { - NSUInteger itemCount = [_dataSource dataController:self rowsInSection:sectionIndex]; + NSUInteger itemCount = counts[sectionIndex]; for (NSUInteger i = 0; i < itemCount; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex]; ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; @@ -585,6 +600,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); + LOG(@"Edit Command - insertSections: %@", sections); dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); @@ -603,7 +619,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; + [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; }); }]; } @@ -645,16 +661,16 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; // remove elements LOG(@"Edit Transaction - moveSection"); - - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); + NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]); + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths); [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // update the section of indexpaths - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; for (NSIndexPath *indexPath in indexPaths) { - [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; + NSIndexPath *updatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:newSection]; + [updatedIndexPaths addObject:updatedIndexPath]; } // Don't re-calculate size for moving @@ -747,7 +763,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; [self willInsertRowsAtIndexPaths:indexPaths]; LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; + [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; }); }]; } @@ -812,11 +828,10 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; for (NSMutableArray *section in nodes) { NSUInteger rowIndex = 0; for (ASCellNode *node in section) { + RETURN_IF_NO_DATASOURCE(); NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - CGRect frame = CGRectZero; - frame.size = [node measureWithSizeRange:constrainedSize].size; - node.frame = frame; + [self _layoutNode:node withConstrainedSize:constrainedSize]; rowIndex += 1; } sectionIndex += 1; @@ -940,3 +955,27 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; } @end + +#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK + +static volatile int64_t _totalExpectedItems = 0; +static volatile int64_t _totalMeasuredNodes = 0; + +@implementation ASDataController (WorkMeasuring) + ++ (void)_didLayoutNode +{ + int64_t measured = OSAtomicIncrement64(&_totalMeasuredNodes); + int64_t expected = _totalExpectedItems; + if (measured % 20 == 0 || measured == expected) { + NSLog(@"Data controller avoided work (underestimated): %lld / %lld", measured, expected); + } +} + ++ (void)_expectToInsertNodes:(NSUInteger)count +{ + OSAtomicAdd64((int64_t)count, &_totalExpectedItems); +} + +@end +#endif diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.h b/AsyncDisplayKit/Details/ASIndexedNodeContext.h index b970d3e997..cdb907bcaf 100644 --- a/AsyncDisplayKit/Details/ASIndexedNodeContext.h +++ b/AsyncDisplayKit/Details/ASIndexedNodeContext.h @@ -29,4 +29,6 @@ */ - (ASCellNode *)allocateNode; ++ (NSArray *)indexPathsFromContexts:(NSArray *)contexts; + @end diff --git a/AsyncDisplayKit/Details/ASIndexedNodeContext.mm b/AsyncDisplayKit/Details/ASIndexedNodeContext.mm index a009d2cbcc..daf9c850a5 100644 --- a/AsyncDisplayKit/Details/ASIndexedNodeContext.mm +++ b/AsyncDisplayKit/Details/ASIndexedNodeContext.mm @@ -48,4 +48,13 @@ return node; } ++ (NSArray *)indexPathsFromContexts:(NSArray *)contexts +{ + NSMutableArray *result = [NSMutableArray arrayWithCapacity:contexts.count]; + for (ASIndexedNodeContext *ctx in contexts) { + [result addObject:ctx.indexPath]; + } + return result; +} + @end diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 092383ad9a..d82073779d 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -415,50 +415,44 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; - (void)dataControllerBeginUpdates:(ASDataController *)dataController { - ASPerformBlockOnMainThread(^{ - [_delegate didBeginUpdatesInRangeController:self]; - }); + ASDisplayNodeAssertMainThread(); + [_delegate didBeginUpdatesInRangeController:self]; } - (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { - ASPerformBlockOnMainThread(^{ - [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; - }); + ASDisplayNodeAssertMainThread(); + [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; } - (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }); + ASDisplayNodeAssertMainThread(); + _rangeIsValid = NO; + [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; } - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }); + ASDisplayNodeAssertMainThread(); + _rangeIsValid = NO; + [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; } - (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }); + ASDisplayNodeAssertMainThread(); + _rangeIsValid = NO; + [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; } - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }); + ASDisplayNodeAssertMainThread(); + _rangeIsValid = NO; + [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; } #pragma mark - Memory Management diff --git a/AsyncDisplayKit/Private/ASDataController+Subclasses.h b/AsyncDisplayKit/Private/ASDataController+Subclasses.h index 7df6e8b2d9..dd3905d96c 100644 --- a/AsyncDisplayKit/Private/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Private/ASDataController+Subclasses.h @@ -53,16 +53,11 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS /** * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. - */ -- (void)batchLayoutNodesFromContexts:(NSArray *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock; - -/** - * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. * - * @discussion Once nodes have loaded their views, we can't layout in the background so this is a chance - * to do so immediately on the main thread. + * This method runs synchronously. + * @param batchCompletion A handler to be run after each batch is completed. It is executed synchronously on the calling thread. */ -- (void)layoutLoadedNodes:(NSArray *)nodes fromContexts:(NSArray *)contexts ofKind:(NSString *)kind; +- (void)batchLayoutNodesFromContexts:(NSArray *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler; /** * Provides the size range for a specific node during the layout process.