[ASDataController] Handle incorrect client code that returns a nil ASCellNode (assert, but use zero-size cell in production).

This commit is contained in:
Scott Goodson
2016-04-06 21:34:06 -07:00
parent a45783cb9d
commit a9b02e8632

View File

@@ -132,7 +132,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
NSAssert(ASDisplayNodeThreadIsMain(), @"Layout of loaded nodes must happen on the main thread."); NSAssert(ASDisplayNodeThreadIsMain(), @"Layout of loaded nodes must happen on the main thread.");
ASDisplayNodeAssertTrue(nodes.count == contexts.count); ASDisplayNodeAssertTrue(nodes.count == contexts.count);
[self _layoutNodes:nodes fromContexts:contexts inIndexesOfRange:NSMakeRange(0, nodes.count) ofKind:kind]; [self _layoutNodes:nodes fromContexts:contexts atIndexesOfRange:NSMakeRange(0, nodes.count) ofKind:kind];
} }
/** /**
@@ -158,7 +158,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
/** /**
* Perform measurement and layout of loaded or unloaded nodes based if they will be layed out on main thread or not * Perform measurement and layout of loaded or unloaded nodes based if they will be layed out on main thread or not
*/ */
- (void)_layoutNodes:(NSArray<ASCellNode *> *)nodes fromContexts:(NSArray<ASIndexedNodeContext *> *)contexts inIndexesOfRange:(NSRange)range ofKind:(NSString *)kind - (void)_layoutNodes:(NSArray<ASCellNode *> *)nodes fromContexts:(NSArray<ASIndexedNodeContext *> *)contexts atIndexesOfRange:(NSRange)range ofKind:(NSString *)kind
{ {
if (_dataSource == nil) { if (_dataSource == nil) {
return; return;
@@ -198,7 +198,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
dispatch_apply(batchCount, queue, ^(size_t i) { dispatch_apply(batchCount, queue, ^(size_t i) {
unsigned long k = j + i; unsigned long k = j + i;
ASCellNode *node = [contexts[k] allocateNode]; ASCellNode *node = [contexts[k] allocateNode];
ASDisplayNodeAssertNotNil(node, @"Node block created nil node"); if (node == nil) {
ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource);
node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps.
}
allocatedNodeBuffer[i] = node; allocatedNodeBuffer[i] = node;
}); });
subarray = [[NSArray alloc] initWithObjects:allocatedNodeBuffer count:batchCount]; subarray = [[NSArray alloc] initWithObjects:allocatedNodeBuffer count:batchCount];
@@ -210,6 +213,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
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()) { if (ASDisplayNodeThreadIsMain()) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0); dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@@ -218,29 +224,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}); });
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[self _layoutNodes:subarray [self _layoutNodes:subarray fromContexts:contexts atIndexesOfRange:batchRange ofKind:kind];
fromContexts:contexts
inIndexesOfRange:NSMakeRange(0, batchCount)
ofKind:kind];
} else { } else {
allocationBlock(); allocationBlock();
[_mainSerialQueue performBlockOnMainThread:^{ [_mainSerialQueue performBlockOnMainThread:^{
[self _layoutNodes:subarray [self _layoutNodes:subarray fromContexts:contexts atIndexesOfRange:batchRange ofKind:kind];
fromContexts:contexts
inIndexesOfRange:NSMakeRange(0, batchCount)
ofKind:kind];
}]; }];
} }
[allocatedNodes addObjectsFromArray:subarray]; [allocatedNodes addObjectsFromArray:subarray];
dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 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 once on a background thread. // We should already have measured loaded nodes before we left the main thread. Layout the remaining ones on a background thread.
[self _layoutNodes:allocatedNodes NSRange asyncBatchRange = NSMakeRange(j, batchCount);
fromContexts:contexts [self _layoutNodes:allocatedNodes fromContexts:contexts atIndexesOfRange:asyncBatchRange ofKind:kind];
inIndexesOfRange:NSMakeRange(j, batchCount)
ofKind:kind];
}); });
} }