diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 8a4a597f96..94ae05d3cb 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -347,14 +347,25 @@ NS_ASSUME_NONNULL_BEGIN * * @param indexPath The index path of the requested node. * - * @returns a node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). Unlike UICollectionView's version, this method + * @returns a node for display at this indexpath. This will be called on the main thread and should not implement reuse (it will be called once per row). Unlike UICollectionView's version, this method * is not called when the row is about to display. */ - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath; + @optional +/** + * + * @param collectionView The sender. + * + * @param indexPath The index path of the requested node. + * + * @returns a block that creates the node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background + * queue) and should not implement reuse (it will be called once per row). + */ +- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath; + /** * Asks the collection view to provide a supplementary node to display in the collection view. * diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 8d7e6fae76..2b089cc936 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -690,6 +690,33 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return node; } + +- (ASDataControllerCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath +{ + if (![_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockAtIndexPath:)]) { + ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; + return ^{ + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); + if (node.layoutDelegate == nil) { + node.layoutDelegate = self; + } + return node; + }; + } + + ASDataControllerCellNodeBlock block = [_asyncDataSource collectionView:self nodeBlockAtIndexPath:indexPath]; + ASDisplayNodeAssertNotNil(block, @"Invalid block, expected nonnull ASDataControllerCellNodeBlock"); + return ^{ + ASCellNode *node = block(); + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + if (node.layoutDelegate == nil) { + node.layoutDelegate = self; + } + return node; + }; +} + - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { ASSizeRange constrainedSize = kInvalidSizeRange; diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index fa992eb1b5..bdf792a33f 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -15,6 +15,12 @@ // This method replaces -collectionView:nodeForItemAtIndexPath: - (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; + +@optional + +// This method replaces -collectionView:nodeBlockForItemAtIndexPath: +- (ASDataControllerCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; + @end @interface ASPagerNode : ASCollectionNode diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index c7db4850ac..486520d267 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -84,6 +84,17 @@ return pageNode; } +- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { + ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); + if (![_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]) { + ASCellNode *node = [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; + return ^{ return node; }; + } + ASDataControllerCellNodeBlock block = [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item]; + ASDisplayNodeAssertNotNil(block, @"Invalid node block. Block should be non-nil."); + return block; +} + - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index bb7fa42062..c63ad12a64 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -322,14 +322,26 @@ NS_ASSUME_NONNULL_BEGIN * * @param indexPath The index path of the requested node. * - * @returns a node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). Unlike UITableView's version, this method + * @returns a node for display at this indexpath. This will be called on the main thread and should not implement reuse (it will be called once per row). Unlike UITableView's version, this method * is not called when the row is about to display. */ - (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath; @optional +/** + * Similar to -tableView:nodeForRowAtIndexPath:. + * + * @param tableView The sender. + * + * @param indexPath The index path of the requested node. + * + * @returns a block that creates the node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background + * queue) and should not implement reuse (it will be called once per row). + */ + +- (ASDataControllerCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath; + /** * Indicator to lock the data source for data fetching in async mode. * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistence or exception diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index a327f7a945..58dfc135ab 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -876,6 +876,33 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return node; } +- (ASDataControllerCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { + if (![_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]) { + ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; + return ^{ + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); + if (node.layoutDelegate == nil) { + node.layoutDelegate = self; + } + return node; + }; + } + + ASDataControllerCellNodeBlock block = [_asyncDataSource tableView:self nodeBlockForRowAtIndexPath:indexPath]; + __weak __typeof__(self) weakSelf = self; + ASDataControllerCellNodeBlock configuredNodeBlock = ^{ + __typeof__(self) strongSelf = weakSelf; + ASCellNode *node = block(); + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + if (node.layoutDelegate == nil) { + node.layoutDelegate = strongSelf; + } + return node; + }; + return configuredNodeBlock; +} + - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { return ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, 0), diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index 7c66ff41fb..01a3f9f694 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -30,6 +30,10 @@ - (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; +@optional + +- (ASDataControllerCellNodeBlock)dataController:(ASCollectionDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; + @end @interface ASCollectionDataController : ASChangeSetDataController diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 9e51ee6a24..adc1548155 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -24,8 +24,8 @@ @end @implementation ASCollectionDataController { - NSMutableDictionary *_pendingNodes; - NSMutableDictionary *_pendingIndexPaths; + NSMutableDictionary *> *_pendingNodes; + NSMutableDictionary *> *_pendingIndexPaths; } - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled @@ -49,7 +49,7 @@ _pendingIndexPaths[kind] = indexPaths; // Measure loaded nodes before leaving the main thread - [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -91,7 +91,7 @@ _pendingIndexPaths[kind] = indexPaths; // Measure loaded nodes before leaving the main thread - [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -132,7 +132,7 @@ _pendingIndexPaths[kind] = indexPaths; // Measure loaded nodes before leaving the main thread - [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } @@ -176,7 +176,14 @@ for (NSUInteger j = 0; j < rowCount; j++) { NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; [indexPaths addObject:indexPath]; - [nodes addObject:[self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]]; + ASDataControllerCellNodeBlock supplementaryCellBlock; + if ([self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]) { + supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; + } else { + ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; + supplementaryCellBlock = ^{ return supplementaryNode; }; + } + [nodes addObject:supplementaryCellBlock]; } } } @@ -189,8 +196,14 @@ for (NSUInteger i = 0; i < rowNum; i++) { NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; [indexPaths addObject:indexPath]; - ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; - [nodes addObject:supplementaryNode]; + ASDataControllerCellNodeBlock supplementaryCellBlock; + if ([self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]) { + supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; + } else { + ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; + supplementaryCellBlock = ^{ return supplementaryNode; }; + } + [nodes addObject:supplementaryCellBlock]; } }]; } diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 5710b22b4e..6630babd08 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -20,12 +20,19 @@ NS_ASSUME_NONNULL_BEGIN @class ASDataController; typedef NSUInteger ASDataControllerAnimationOptions; + +/** + * ASCellNode creation block. Used to lazily create the ASCellNode instance for a specified indexPath. + */ +typedef ASCellNode * _Nonnull(^ASDataControllerCellNodeBlock)(); + FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; /** Data source for data controller It will be invoked in the same thread as the api call of ASDataController. */ + @protocol ASDataControllerSource /** @@ -33,6 +40,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath; +/** + Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path. + */ +- (ASDataControllerCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath; + /** The constrained size range for layout. */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 53b3715874..fa4006255d 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -39,10 +39,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. BOOL _asyncDataFetchingEnabled; + BOOL _delegateDidInsertNodes; BOOL _delegateDidDeleteNodes; BOOL _delegateDidInsertSections; BOOL _delegateDidDeleteSections; + + BOOL _dataSourceImplementsAsyncNodeAtIndexPath; } @property (atomic, assign) NSUInteger batchUpdateCounter; @@ -94,6 +97,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; } +- (void)setDataSource:(id)dataSource { + if (_dataSource == dataSource) { + return; + } + + _dataSource = dataSource; + // This probably won't be sufficient to tell if we should call the node block + _dataSourceImplementsAsyncNodeAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:nodeBlockAtIndexPath:)]; +} + + (NSUInteger)parallelProcessorCount { static NSUInteger parallelProcessorCount; @@ -108,7 +121,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; @@ -117,12 +130,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange]; - [self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock]; } } -- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { +- (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { NSAssert(NSThread.isMainThread, @"Main thread layout must be on the main thread."); [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, __unused BOOL * stop) { @@ -159,22 +171,50 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (!nodes.count) { return; } - + + NSUInteger nodeCount = nodes.count; + NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:nodeCount]; dispatch_group_t layoutGroup = dispatch_group_create(); - ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodes.count); + ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodeCount); + BOOL isMainThread = [NSThread isMainThread]; for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j); - - for (NSUInteger k = j; k < j + batchCount; k++) { - ASCellNode *node = nodes[k]; - if (!node.isNodeLoaded) { - nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; + + + if (isMainThread) { + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + for (NSUInteger k = j; k < j + batchCount; k++) { + ASDataControllerCellNodeBlock cellBlock = nodes[k]; + ASCellNode *node = cellBlock(); + ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); + [allocatedNodes addObject:node]; + if (!node.isNodeLoaded) { + nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; + } + } + dispatch_semaphore_signal(sema); + }); + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); + [self layoutLoadedNodes:allocatedNodes ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + } else { + for (NSUInteger k = j; k < j + batchCount; k++) { + ASDataControllerCellNodeBlock cellBlock = nodes[k]; + ASCellNode *node = cellBlock(); + ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); + [allocatedNodes addObject:node]; + if (!node.isNodeLoaded) { + nodeBoundSizes[k] = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPaths[k]]; + } } + [_mainSerialQueue performBlockOnMainThread:^{ + [self layoutLoadedNodes:allocatedNodes ofKind:kind atIndexPaths:[indexPaths subarrayWithRange:NSMakeRange(j, batchCount)]]; + }]; } - + dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (NSUInteger k = j; k < j + batchCount; k++) { - ASCellNode *node = nodes[k]; + ASCellNode *node = allocatedNodes[k]; // Only measure nodes whose views aren't loaded, since we're in the background. // We should already have measured loaded nodes before we left the main thread, using layoutLoadedNodes:ofKind:atIndexPaths: if (!node.isNodeLoaded) { @@ -183,13 +223,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } }); } - + // Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated. dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); free(nodeBoundSizes); if (completionBlock) { - completionBlock(nodes, indexPaths); + completionBlock(allocatedNodes, indexPaths); } } @@ -382,9 +422,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; - - // Measure nodes whose views are loaded before we leave the main thread - [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; + +// if (!_dataSourceImplementsAsyncNodeAtIndexPath) { +// // Measure nodes whose views are loaded before we leave the main thread +// [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; +// } // Allow subclasses to perform setup before going into the edit transaction [self prepareForReloadData]; @@ -409,9 +451,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; - + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - + +// if (_dataSourceImplementsAsyncNodeAtIndexPath) { +// NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:updatedNodes.count]; +// for (NSUInteger i = 0; i < updatedNodes.count; i++) { +// ASDataControllerCellNodeBlock cellCreationBlock = updatedNodes[i]; +// __kindof ASCellNode *cellNode = cellCreationBlock(); +// ASDisplayNodeAssertFalse(cellNode.isNodeLoaded); +// [allocatedNodes addObject:cellNode]; +// } +// [self _batchLayoutNodes:allocatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } else { +// [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } + if (completion) { dispatch_async(dispatch_get_main_queue(), completion); } @@ -462,12 +517,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; { [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx]; - NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx]; for (NSUInteger i = 0; i < rowNum; i++) { NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i]; [indexPaths addObject:indexPath]; - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + if (_dataSourceImplementsAsyncNodeAtIndexPath) { + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + } else { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + } } }]; } @@ -482,12 +540,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self]; for (NSUInteger i = 0; i < sectionNum; i++) { NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:i]; - NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i]; for (NSUInteger j = 0; j < rowNum; j++) { NSIndexPath *indexPath = [sectionIndexPath indexPathByAddingIndex:j]; [indexPaths addObject:indexPath]; - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + if (_dataSourceImplementsAsyncNodeAtIndexPath) { + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + } else { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + } } } } @@ -578,8 +639,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; - // Measure nodes whose views are loaded before we leave the main thread - [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; +// if (!_dataSourceImplementsAsyncNodeAtIndexPath) { +// // Measure nodes whose views are loaded before we leave the main thread +// [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; +// } [self prepareForInsertSections:sections]; @@ -591,9 +654,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + +// if (_dataSourceImplementsAsyncNodeAtIndexPath) { +// NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:updatedNodes.count]; +// for (NSUInteger i = 0; i < updatedNodes.count; i++) { +// ASDataControllerCellNodeBlock cellCreationBlock = updatedNodes[i]; +// __kindof ASCellNode *cellNode = cellCreationBlock(); +// ASDisplayNodeAssertFalse(cellNode.isNodeLoaded); +// [allocatedNodes addObject:cellNode]; +// } +// [self _batchLayoutNodes:allocatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } else { +// [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } }]; }]; }]; @@ -635,9 +711,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Dispatch to sizing queue in order to guarantee that any in-progress sizing operations from prior edits have completed. // For example, if an initial -reloadData call is quickly followed by -reloadSections, sizing the initial set may not be done // at this time. Thus _editingNodes could be empty and crash in ASIndexPathsForMultidimensional[...] - - // Measure nodes whose views are loaded before we leave the main thread - [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; +// if (!_dataSourceImplementsAsyncNodeAtIndexPath) { +// // Measure nodes whose views are loaded before we leave the main thread +// [self layoutLoadedNodes:updatedNodes ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths]; +// } [self prepareForReloadSections:sections]; @@ -649,9 +726,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - + // reinsert the elements [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + +// if (_dataSourceImplementsAsyncNodeAtIndexPath) { +// NSMutableArray *allocatedNodes = [NSMutableArray arrayWithCapacity:updatedNodes.count]; +// for (NSUInteger i = 0; i < updatedNodes.count; i++) { +// ASDataControllerCellNodeBlock cellCreationBlock = updatedNodes[i]; +// __kindof ASCellNode *cellNode = cellCreationBlock(); +// ASDisplayNodeAssertFalse(cellNode.isNodeLoaded); +// [allocatedNodes addObject:cellNode]; +// } +// [self _batchLayoutNodes:allocatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } else { +// [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; +// } }]; }]; }]; @@ -747,12 +837,16 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; for (NSUInteger i = 0; i < sortedIndexPaths.count; i++) { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]]; + if (_dataSourceImplementsAsyncNodeAtIndexPath) { + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:sortedIndexPaths[i]]]; + } else { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:sortedIndexPaths[i]]]; + } } // Measure nodes whose views are loaded before we leave the main thread - [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; - +// [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; + [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; @@ -797,11 +891,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + if (_dataSourceImplementsAsyncNodeAtIndexPath) { + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + } else { + [nodes addObject:[_dataSource dataController:self nodeAtIndexPath:indexPath]]; + } } // Measure nodes whose views are loaded before we leave the main thread - [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; +// [self layoutLoadedNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index 6f3594ecc8..bf391db103 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -81,8 +81,9 @@ @end @implementation ASDelegateProxy { - id __weak _target; id __weak _interceptor; +@protected + NSObject * __weak _target; } - (instancetype)initWithTarget:(id )target interceptor:(id )interceptor @@ -130,4 +131,12 @@ return NO; } +- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)selector +{ + if ([_target isEqual:[NSNull null]]) { + return nil; + } + return [_target methodSignatureForSelector:selector]; +} + @end diff --git a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m index a9c5614e5f..76b9cc4674 100644 --- a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m @@ -25,6 +25,10 @@ return [[ASCellNode alloc] init]; } +- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { + return ^{ return [[ASCellNode alloc] init]; }; +} + - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return 0; diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.m b/AsyncDisplayKitTests/ASCollectionViewTests.m index 6059fabad6..6c4be4ec2c 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewTests.m @@ -35,6 +35,15 @@ return textCellNode; } + +- (ASDataControllerCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockAtIndexPath:(NSIndexPath *)indexPath { + return ^{ + ASTextCellNode *textCellNode = [ASTextCellNode new]; + textCellNode.text = indexPath.description; + return textCellNode; + }; +} + - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return self.numberOfSections; } diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 676634e106..c9f581df0e 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -72,6 +72,11 @@ return nil; } +- (ASDataControllerCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return nil; +} + - (void)dealloc { if (_willDeallocBlock) { @@ -121,6 +126,16 @@ return textCellNode; } + +- (ASDataControllerCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return ^{ + ASTestTextCellNode *textCellNode = [ASTestTextCellNode new]; + textCellNode.text = indexPath.description; + return textCellNode; + }; +} + @end @interface ASTableViewTests : XCTestCase