mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
[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:
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
|
||||
@interface ASCollectionDataController : ASChangeSetDataController
|
||||
|
||||
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)dataSource NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,4 +29,6 @@
|
||||
*/
|
||||
- (ASCellNode *)allocateNode;
|
||||
|
||||
+ (NSArray<NSIndexPath *> *)indexPathsFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user