[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 -
#pragma mark ASCollectionView. #pragma mark ASCollectionView.
@interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView, ASDataControllerEnvironmentDelegate> { @interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASCollectionDataControllerSource, ASCellNodeInteractionDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView, ASDataControllerEnvironmentDelegate> {
ASCollectionViewProxy *_proxyDataSource; ASCollectionViewProxy *_proxyDataSource;
ASCollectionViewProxy *_proxyDelegate; ASCollectionViewProxy *_proxyDelegate;
@@ -232,9 +232,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
_rangeController.delegate = self; _rangeController.delegate = self;
_rangeController.layoutController = _layoutController; _rangeController.layoutController = _layoutController;
_dataController = [[ASCollectionDataController alloc] init]; _dataController = [[ASCollectionDataController alloc] initWithDataSource:self];
_dataController.delegate = _rangeController; _dataController.delegate = _rangeController;
_dataController.dataSource = self;
_dataController.environmentDelegate = self; _dataController.environmentDelegate = self;
_batchContext = [[ASBatchContext alloc] init]; _batchContext = [[ASBatchContext alloc] init];

View File

@@ -189,8 +189,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
_rangeController.dataSource = self; _rangeController.dataSource = self;
_rangeController.delegate = self; _rangeController.delegate = self;
_dataController = [[dataControllerClass alloc] init]; _dataController = [[dataControllerClass alloc] initWithDataSource:self];
_dataController.dataSource = self;
_dataController.delegate = _rangeController; _dataController.delegate = _rangeController;
_dataController.environmentDelegate = self; _dataController.environmentDelegate = self;

View File

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

View File

@@ -31,11 +31,14 @@
NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingContexts; NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingContexts;
} }
- (instancetype)init - (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)dataSource
{ {
self = [super init]; self = [super initWithDataSource:dataSource];
if (self != nil) { if (self != nil) {
_pendingContexts = [NSMutableDictionary dictionary]; _pendingContexts = [NSMutableDictionary dictionary];
_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)];
ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [dataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]);
} }
return self; return self;
} }
@@ -69,7 +72,7 @@
} }
[self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; [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]; [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}]; }];
}]; }];
@@ -95,7 +98,7 @@
} }
[self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; [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]; [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}]; }];
}]; }];
@@ -145,7 +148,7 @@
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths - (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{ {
[_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, BOOL * _Nonnull stop) { [_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]; [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]; [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 - (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts environmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection
{ {
ASCellNodeBlock supplementaryCellBlock; ASCellNodeBlock supplementaryCellBlock;
if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) {
supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath];
} else { } else {
ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath];
supplementaryCellBlock = ^{ return supplementaryNode; }; supplementaryCellBlock = ^{ return supplementaryNode; };
} }
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock
indexPath:indexPath indexPath:indexPath
constrainedSize:constrainedSize constrainedSize:constrainedSize
environmentTraitCollection:environmentTraitCollection]; environmentTraitCollection:environmentTraitCollection];
[contexts addObject:context]; [contexts addObject:context];
} }
#pragma mark - Sizing query #pragma mark - Sizing query
@@ -296,12 +299,4 @@
return (id<ASCollectionDataControllerSource>)self.dataSource; 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 @end

View File

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

View File

@@ -23,6 +23,9 @@
//#define LOG(...) NSLog(__VA_ARGS__) //#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...) #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)) #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; const static NSUInteger kASDataControllerSizingCountPerProcessor = 5;
@@ -31,6 +34,13 @@ const static char * kASDataControllerEditingQueueContext = "kASDataControllerEdi
NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK
@interface ASDataController (AvoidedWorkMeasuring)
+ (void)_didLayoutNode;
+ (void)_expectToInsertNodes:(NSUInteger)count;
@end
#endif
@interface ASDataController () { @interface ASDataController () {
NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. 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. 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 #pragma mark - Lifecycle
- (instancetype)init - (instancetype)initWithDataSource:(id<ASDataControllerSource>)dataSource
{ {
if (!(self = [super init])) { if (!(self = [super init])) {
return nil; return nil;
} }
ASDisplayNodeAssert(![self isMemberOfClass:[ASDataController class]], @"ASDataController is an abstract class and should not be instantiated. Instantiate a subclass instead."); ASDisplayNodeAssert(![self isMemberOfClass:[ASDataController class]], @"ASDataController is an abstract class and should not be instantiated. Instantiate a subclass instead.");
_dataSource = dataSource;
_completedNodes = [NSMutableDictionary dictionary]; _completedNodes = [NSMutableDictionary dictionary];
_editingNodes = [NSMutableDictionary dictionary]; _editingNodes = [NSMutableDictionary dictionary];
@@ -87,6 +99,13 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
return self; return self;
} }
- (instancetype)init
{
ASDisplayNodeFailAssert(@"Failed to call designated initializer.");
id<ASDataControllerSource> fakeDataSource = nil;
return [self initWithDataSource:fakeDataSource];
}
- (void)setDelegate:(id<ASDataControllerDelegate>)delegate - (void)setDelegate:(id<ASDataControllerDelegate>)delegate
{ {
if (_delegate == delegate) { if (_delegate == delegate) {
@@ -116,9 +135,12 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
#pragma mark - Cell Layout #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; ASSERT_ON_EDITING_QUEUE;
#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK
[ASDataController _expectToInsertNodes:contexts.count];
#endif
NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
NSUInteger count = contexts.count; NSUInteger count = contexts.count;
@@ -127,7 +149,9 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
for (NSUInteger i = 0; i < count; i += blockSize) { for (NSUInteger i = 0; i < count; i += blockSize) {
NSRange batchedRange = NSMakeRange(i, MIN(count - i, blockSize)); NSRange batchedRange = NSMakeRange(i, MIN(count - i, blockSize));
NSArray<ASIndexedNodeContext *> *batchedContexts = [contexts subarrayWithRange:batchedRange]; 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. * 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; 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 // Insert finished nodes into data storage
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; [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; ASSERT_ON_EDITING_QUEUE;
if (!contexts.count || _dataSource == nil) { NSUInteger nodeCount = contexts.count;
return; 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 *)); __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *));
for (NSUInteger j = 0; j < nodeCount; j += kASDataControllerSizingCountPerProcessor) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, nodeCount - j); dispatch_apply(nodeCount, queue, ^(size_t i) {
RETURN_IF_NO_DATASOURCE();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // Allocate the node.
dispatch_apply(batchCount, queue, ^(size_t i) { ASIndexedNodeContext *context = contexts[i];
unsigned long k = j + i; ASCellNode *node = [context allocateNode];
ASIndexedNodeContext *context = contexts[k]; if (node == nil) {
ASCellNode *node = [context allocateNode]; ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource);
if (node == nil) { node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps.
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; [self _layoutNode:node withConstrainedSize:context.constrainedSize];
allocatedNodeBuffer[k] = node; #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 // Create nodes array
NSArray *allocatedNodes = [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount]; NSArray *nodes = canceled ? nil : [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 < nodeCount; i++) { for (int i = 0; i < nodeCount; i++) {
allocatedContextIndexPaths[i] = nil;
allocatedNodeBuffer[i] = nil; allocatedNodeBuffer[i] = nil;
} }
free(allocatedContextIndexPaths);
free(allocatedNodeBuffer); free(allocatedNodeBuffer);
if (completionBlock) { return nodes;
completionBlock(allocatedNodes, indexPaths);
}
} }
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath - (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])); LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForTwoDimensionalArray(_editingNodes[kind]));
NSMutableArray *editingNodes = _editingNodes[kind]; ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths);
_editingNodes[kind] = editingNodes;
[_mainSerialQueue performBlockOnMainThread:^{ [_mainSerialQueue performBlockOnMainThread:^{
NSMutableArray *allNodes = _completedNodes[kind]; NSMutableArray *allNodes = _completedNodes[kind];
@@ -383,14 +400,11 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); 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)]; NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; 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 // Allow subclasses to perform setup before going into the edit transaction
[self prepareForReloadData]; [self prepareForReloadData];
@@ -416,10 +430,10 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
} }
[self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions]; [self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions];
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
if (completion) { if (completion) {
dispatch_async(dispatch_get_main_queue(), completion); [_mainSerialQueue performBlockOnMainThread:completion];
} }
}); });
if (synchronously) { if (synchronously) {
@@ -455,10 +469,11 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment]; id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
std::vector<NSInteger> counts = [self itemCountsFromDataSource];
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array]; NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
[indexSet enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { [indexSet enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { 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++) { for (NSUInteger i = 0; i < itemCount; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex]; NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex];
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
@@ -585,6 +600,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
{ {
[self performEditCommandWithBlock:^{ [self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - insertSections: %@", sections); LOG(@"Edit Command - insertSections: %@", sections);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
@@ -603,7 +619,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; [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 // remove elements
LOG(@"Edit Transaction - moveSection"); LOG(@"Edit Transaction - moveSection");
NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// update the section of indexpaths // update the section of indexpaths
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection];
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in indexPaths) { 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 // Don't re-calculate size for moving
@@ -747,7 +763,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
[self willInsertRowsAtIndexPaths:indexPaths]; [self willInsertRowsAtIndexPaths:indexPaths];
LOG(@"Edit Transaction - insertRows: %@", 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) { for (NSMutableArray *section in nodes) {
NSUInteger rowIndex = 0; NSUInteger rowIndex = 0;
for (ASCellNode *node in section) { for (ASCellNode *node in section) {
RETURN_IF_NO_DATASOURCE();
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
CGRect frame = CGRectZero; [self _layoutNode:node withConstrainedSize:constrainedSize];
frame.size = [node measureWithSizeRange:constrainedSize].size;
node.frame = frame;
rowIndex += 1; rowIndex += 1;
} }
sectionIndex += 1; sectionIndex += 1;
@@ -940,3 +955,27 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
} }
@end @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; - (ASCellNode *)allocateNode;
+ (NSArray<NSIndexPath *> *)indexPathsFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts;
@end @end

View File

@@ -48,4 +48,13 @@
return node; 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 @end

View File

@@ -415,50 +415,44 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
- (void)dataControllerBeginUpdates:(ASDataController *)dataController - (void)dataControllerBeginUpdates:(ASDataController *)dataController
{ {
ASPerformBlockOnMainThread(^{ ASDisplayNodeAssertMainThread();
[_delegate didBeginUpdatesInRangeController:self]; [_delegate didBeginUpdatesInRangeController:self];
});
} }
- (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion - (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
{ {
ASPerformBlockOnMainThread(^{ ASDisplayNodeAssertMainThread();
[_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion]; [_delegate rangeController:self didEndUpdatesAnimated:animated completion:completion];
});
} }
- (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)dataController:(ASDataController *)dataController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path"); ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path");
ASPerformBlockOnMainThread(^{ ASDisplayNodeAssertMainThread();
_rangeIsValid = NO; _rangeIsValid = NO;
[_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; [_delegate rangeController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
});
} }
- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASPerformBlockOnMainThread(^{ ASDisplayNodeAssertMainThread();
_rangeIsValid = NO; _rangeIsValid = NO;
[_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; [_delegate rangeController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
});
} }
- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections");
ASPerformBlockOnMainThread(^{ ASDisplayNodeAssertMainThread();
_rangeIsValid = NO; _rangeIsValid = NO;
[_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
});
} }
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASPerformBlockOnMainThread(^{ ASDisplayNodeAssertMainThread();
_rangeIsValid = NO; _rangeIsValid = NO;
[_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
});
} }
#pragma mark - Memory Management #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:`. * 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 * This method runs synchronously.
* to do so immediately on the main thread. * @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. * Provides the size range for a specific node during the layout process.