[ASDisplayNode] Always layout nodes on a background thread (#1907)

* Always layout nodes on a background thread

* Remove semaphore in ASDataController for allocating nodes and layout

* Fix variable not used error

* Remove overhead to create subarray of contexts of nodes while layout nodes

* Remove extra allocation of allocatedNodes and indexPaths array
This commit is contained in:
Michael Schneider 2016-07-14 21:35:58 -07:00 committed by appleguy
parent f95790f280
commit a8c5ac138d
2 changed files with 32 additions and 72 deletions

View File

@ -791,7 +791,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
}; };
// TODO ihm: Can we always push the measure to the background thread and remove the parameter from the API? // TODO ihm: Can we always push the measure to the background thread and remove the parameter from the API?
if (shouldMeasureAsync) { if (ASDisplayNodeThreadIsMain()) {
ASPerformBlockOnBackgroundThread(transitionBlock); ASPerformBlockOnBackgroundThread(transitionBlock);
} else { } else {
transitionBlock(); transitionBlock();

View File

@ -165,18 +165,14 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
return; return;
} }
// For any given layout pass that occurs, this method will be called at least twice, once on the main thread and // Layout node on whatever thread we are on. We handle the trampoline to the main thread in case the node is
// the background, to result in complete coverage of both loaded and unloaded nodes // already loaded
BOOL isMainThread = ASDisplayNodeThreadIsMain();
for (NSUInteger k = range.location; k < NSMaxRange(range); k++) { for (NSUInteger k = range.location; k < NSMaxRange(range); k++) {
ASCellNode *node = nodes[k]; ASCellNode *node = nodes[k];
// Only nodes that are loaded should be layout on the main thread
if (node.isNodeLoaded == isMainThread) {
ASIndexedNodeContext *context = contexts[k]; ASIndexedNodeContext *context = contexts[k];
[self _layoutNode:node withConstrainedSize:context.constrainedSize]; [self _layoutNode:node withConstrainedSize:context.constrainedSize];
} }
} }
}
- (void)_layoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock - (void)_layoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock
{ {
@ -187,18 +183,12 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
} }
NSUInteger nodeCount = contexts.count; NSUInteger nodeCount = contexts.count;
NSMutableArray<ASCellNode *> *allocatedNodes = [NSMutableArray<ASCellNode *> arrayWithCapacity:nodeCount]; __strong NSIndexPath **allocatedContextIndexPaths = (__strong NSIndexPath **)calloc(nodeCount, sizeof(NSIndexPath *));
dispatch_group_t layoutGroup = dispatch_group_create(); __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *));
for (NSUInteger j = 0; j < nodeCount; j += kASDataControllerSizingCountPerProcessor) { for (NSUInteger j = 0; j < nodeCount; j += kASDataControllerSizingCountPerProcessor) {
NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, nodeCount - j); NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, nodeCount - j);
// Allocate nodes concurrently.
__block NSArray *subarrayOfContexts;
__block NSArray *subarrayOfNodes;
dispatch_block_t allocationBlock = ^{
__strong ASIndexedNodeContext **allocatedContextBuffer = (__strong ASIndexedNodeContext **)calloc(batchCount, sizeof(ASIndexedNodeContext *));
__strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(batchCount, sizeof(ASCellNode *));
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(batchCount, queue, ^(size_t i) { dispatch_apply(batchCount, queue, ^(size_t i) {
unsigned long k = j + i; unsigned long k = j + i;
@ -208,57 +198,27 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource);
node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps.
} }
allocatedNodeBuffer[i] = node;
allocatedContextBuffer[i] = context; allocatedContextIndexPaths[k] = context.indexPath;
allocatedNodeBuffer[k] = node;
[self _layoutNode:node withConstrainedSize:context.constrainedSize];
}); });
subarrayOfNodes = [NSArray arrayWithObjects:allocatedNodeBuffer count:batchCount]; }
subarrayOfContexts = [NSArray arrayWithObjects:allocatedContextBuffer count:batchCount];
// Create nodes and indexPaths array's
NSArray *allocatedNodes = [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount];
NSArray *indexPaths = [NSArray arrayWithObjects:allocatedContextIndexPaths count:nodeCount];
// Nil out buffer indexes to allow arc to free the stored cells. // Nil out buffer indexes to allow arc to free the stored cells.
for (int i = 0; i < batchCount; i++) { for (int i = 0; i < nodeCount; i++) {
allocatedContextBuffer[i] = nil; allocatedContextIndexPaths[i] = nil;
allocatedNodeBuffer[i] = nil; allocatedNodeBuffer[i] = nil;
} }
free(allocatedContextBuffer); free(allocatedContextIndexPaths);
free(allocatedNodeBuffer); free(allocatedNodeBuffer);
};
// Run the allocation block to concurrently create the cell nodes. Then, handle layout for nodes that are already loaded
// (e.g. the dataSource may have provided cells that have been used before), which must do layout on the main thread.
NSRange batchRange = NSMakeRange(0, batchCount);
if (ASDisplayNodeThreadIsMain()) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
allocationBlock();
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[self _layoutNodes:subarrayOfNodes fromContexts:subarrayOfContexts atIndexesOfRange:batchRange ofKind:kind];
} else {
allocationBlock();
[_mainSerialQueue performBlockOnMainThread:^{
[self _layoutNodes:subarrayOfNodes fromContexts:subarrayOfContexts atIndexesOfRange:batchRange ofKind:kind];
}];
}
[allocatedNodes addObjectsFromArray:subarrayOfNodes];
dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// We should already have measured loaded nodes before we left the main thread. Layout the remaining ones on a background thread.
NSRange asyncBatchRange = NSMakeRange(j, batchCount);
[self _layoutNodes:allocatedNodes fromContexts:contexts atIndexesOfRange:asyncBatchRange ofKind:kind];
});
}
// Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated.
dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER);
if (completionBlock) { if (completionBlock) {
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:nodeCount];
for (ASIndexedNodeContext *context in contexts) {
[indexPaths addObject:context.indexPath];
}
completionBlock(allocatedNodes, indexPaths); completionBlock(allocatedNodes, indexPaths);
} }
} }