[ASDataController] Cancel if we lose our data source, fix bugs (#1987)

[ASRangeController] We're already on main thread, remove blocks

Make data source read-only, clarify what's asynchronous

[ASDataController] Clean up some interfaces

[ASDataController] A little more cleanup

[ASDataController] Cleanup

[ASDataController] Restore some changes, exit more often

[ASDataController] Use item counts that we already have rather than requerying them

[ASDataController] Revert weakifications

[ASDataController] Add a mechanism to measure how much work we avoided
This commit is contained in:
Adlai Holler
2016-08-05 15:39:33 -07:00
committed by GitHub
parent 39da5d2cb0
commit 1fbf8ad073
10 changed files with 159 additions and 123 deletions

View File

@@ -87,7 +87,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
#pragma mark -
#pragma mark ASCollectionView.
@interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView, ASDataControllerEnvironmentDelegate> {
@interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASCollectionDataControllerSource, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView, ASDataControllerEnvironmentDelegate> {
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];

View File

@@ -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;

View File

@@ -40,6 +40,8 @@
@interface ASCollectionDataController : ASChangeSetDataController
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)dataSource NS_DESIGNATED_INITIALIZER;
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
@end

View File

@@ -31,11 +31,14 @@
NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingContexts;
}
- (instancetype)init
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)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<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *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<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
}];
@@ -145,7 +148,7 @@
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
[_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, BOOL * _Nonnull stop) {
[self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
}];
@@ -178,7 +181,7 @@
}
}
[self batchLayoutNodesFromContexts:reinsertedContexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
}
@@ -240,20 +243,20 @@
- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)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; };
}
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];
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<ASCollectionDataControllerSource>)self.dataSource;
}
- (void)setDataSource:(id<ASDataControllerSource>)dataSource
{
[super setDataSource:dataSource];
_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)];
ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [self.collectionDataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]);
}
@end

View File

@@ -109,10 +109,12 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
@protocol ASFlowLayoutControllerDataSource;
@interface ASDataController : ASDealloc2MainObject <ASFlowLayoutControllerDataSource>
- (instancetype)initWithDataSource:(id<ASDataControllerSource>)dataSource NS_DESIGNATED_INITIALIZER;
/**
Data source for fetching data info.
*/
@property (nonatomic, weak) id<ASDataControllerSource> dataSource;
@property (nonatomic, weak, readonly) id<ASDataControllerSource> dataSource;
/**
Delegate to notify when data is updated.

View File

@@ -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<ASDataControllerSource>)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<ASDataControllerSource> fakeDataSource = nil;
return [self initWithDataSource:fakeDataSource];
}
- (void)setDelegate:(id<ASDataControllerDelegate>)delegate
{
if (_delegate == delegate) {
@@ -116,9 +135,12 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
#pragma mark - Cell Layout
- (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock
- (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)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<ASIndexedNodeContext *> *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<ASIndexedNodeContext *> *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
- (void)_batchLayoutAndInsertNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASSERT_ON_EDITING_QUEUE;
[self batchLayoutNodesFromContexts:contexts ofKind:ASDataControllerRowNodeKind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
// Insert finished nodes into data storage
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}
- (void)_layoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock
- (NSArray<ASCellNode *> *)_layoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)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.
}
// 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.
}
allocatedContextIndexPaths[k] = context.indexPath;
allocatedNodeBuffer[k] = node;
[self _layoutNode:node withConstrainedSize:context.constrainedSize];
#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK
[ASDataController _didLayoutNode];
#endif
allocatedNodeBuffer[i] = node;
});
[self _layoutNode:node withConstrainedSize:context.constrainedSize];
});
}
BOOL canceled = _dataSource == nil;
// Create nodes and indexPaths array's
NSArray *allocatedNodes = [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount];
NSArray *indexPaths = [NSArray arrayWithObjects:allocatedContextIndexPaths count:nodeCount];
// 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,14 +400,11 @@ 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<ASIndexedNodeContext *> *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<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
std::vector<NSInteger> counts = [self itemCountsFromDataSource];
NSMutableArray<ASIndexedNodeContext *> *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

View File

@@ -29,4 +29,6 @@
*/
- (ASCellNode *)allocateNode;
+ (NSArray<NSIndexPath *> *)indexPathsFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts;
@end

View File

@@ -48,4 +48,13 @@
return node;
}
+ (NSArray<NSIndexPath *> *)indexPathsFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts
{
NSMutableArray *result = [NSMutableArray arrayWithCapacity:contexts.count];
for (ASIndexedNodeContext *ctx in contexts) {
[result addObject:ctx.indexPath];
}
return result;
}
@end

View File

@@ -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

View File

@@ -53,16 +53,11 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCellNode *> *nodes, NS
/**
* Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`.
*/
- (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)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<ASCellNode *> *)nodes fromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind;
- (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler;
/**
* Provides the size range for a specific node during the layout process.