mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
[ASDataController] Remove asyncDataFetching Option, Cleanup (#1794)
* [ASDataController] Add some assertions to clarify what queues things happen on * [ASCollectionDataController] Optimize willReloadData * [ASDataController] Minor optimizations, no functional changes * [ASDataController] Always reload data on _editingTransactionQueue * [ASDataController] Remove async data fetching option, deprecate callbacks * [ASDataController] Not mutable * [ASMultidimensionalArrayUtils] Use fast enumeration * Optimize ASMultidimensionalArrayUtils
This commit is contained in:
@@ -407,8 +407,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
|
||||
*/
|
||||
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView;
|
||||
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Indicator to unlock the data source for data fetching in async mode.
|
||||
@@ -416,8 +417,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
|
||||
*/
|
||||
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView;
|
||||
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -112,7 +112,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
BOOL _performingBatchUpdates;
|
||||
NSMutableArray *_batchUpdateBlocks;
|
||||
|
||||
BOOL _asyncDataFetchingEnabled;
|
||||
_ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only
|
||||
BOOL _isDeallocating;
|
||||
|
||||
@@ -155,14 +154,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
unsigned int asyncDataSourceNodeForItemAtIndexPath:1;
|
||||
unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1;
|
||||
unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1;
|
||||
unsigned int asyncDataSourceCollectionViewLockDataSource:1;
|
||||
unsigned int asyncDataSourceCollectionViewUnlockDataSource:1;
|
||||
unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1;
|
||||
} _asyncDataSourceFlags;
|
||||
}
|
||||
|
||||
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
||||
|
||||
// Used only when ASCollectionView is created directly rather than through ASCollectionNode.
|
||||
// We create a node so that logic related to appearance, memory management, etc can be located there
|
||||
// for both the node-based and view-based version of the table.
|
||||
@@ -231,7 +226,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
_rangeController.delegate = self;
|
||||
_rangeController.layoutController = _layoutController;
|
||||
|
||||
_dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:NO];
|
||||
_dataController = [[ASCollectionDataController alloc] init];
|
||||
_dataController.delegate = _rangeController;
|
||||
_dataController.dataSource = self;
|
||||
_dataController.environmentDelegate = self;
|
||||
@@ -240,9 +235,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
_leadingScreensForBatching = 2.0;
|
||||
|
||||
_asyncDataFetchingEnabled = NO;
|
||||
_asyncDataSourceLocked = NO;
|
||||
|
||||
_performingBatchUpdates = NO;
|
||||
_batchUpdateBlocks = [NSMutableArray array];
|
||||
|
||||
@@ -375,9 +367,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
_asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];;
|
||||
_asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
|
||||
|
||||
// Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath:
|
||||
ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath);
|
||||
@@ -937,26 +927,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dataControllerLockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked");
|
||||
|
||||
self.asyncDataSourceLocked = YES;
|
||||
if (_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource) {
|
||||
[_asyncDataSource collectionViewLockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dataControllerUnlockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked");
|
||||
|
||||
self.asyncDataSourceLocked = NO;
|
||||
if (_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource) {
|
||||
[_asyncDataSource collectionViewUnlockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (id<ASEnvironment>)dataControllerEnvironment
|
||||
{
|
||||
if (self.collectionNode) {
|
||||
|
||||
@@ -44,18 +44,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* The frame of the table view changes as table cells are added and deleted.
|
||||
*
|
||||
* @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants.
|
||||
*
|
||||
* @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op.
|
||||
*
|
||||
* @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and
|
||||
* `tableView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically
|
||||
* from calling thread.
|
||||
* Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for
|
||||
* large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically,
|
||||
* we will lock the data source through `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` after the data fetching.
|
||||
* The application should not update the data source while the data source is locked, to keep data consistence.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style;
|
||||
|
||||
/**
|
||||
* Tuning parameters for a range type in full mode.
|
||||
@@ -363,8 +353,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param tableView The sender.
|
||||
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
|
||||
*/
|
||||
- (void)tableViewLockDataSource:(ASTableView *)tableView;
|
||||
- (void)tableViewLockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Indicator to unlock the data source for data fetching in asyn mode.
|
||||
@@ -372,8 +363,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param tableView The sender.
|
||||
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
|
||||
*/
|
||||
- (void)tableViewUnlockDataSource:(ASTableView *)tableView;
|
||||
- (void)tableViewUnlockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
@end
|
||||
|
||||
@@ -458,4 +450,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@protocol ASTableViewDelegate <ASTableDelegate>
|
||||
@end
|
||||
|
||||
@interface ASTableView (Deprecated)
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -100,8 +100,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
ASRangeController *_rangeController;
|
||||
|
||||
BOOL _asyncDataFetchingEnabled;
|
||||
|
||||
ASBatchContext *_batchContext;
|
||||
|
||||
NSIndexPath *_pendingVisibleIndexPath;
|
||||
@@ -133,12 +131,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
unsigned int asyncDataSourceNumberOfSectionsInTableView:1;
|
||||
unsigned int asyncDataSourceTableViewNodeBlockForRowAtIndexPath:1;
|
||||
unsigned int asyncDataSourceTableViewNodeForRowAtIndexPath:1;
|
||||
unsigned int asyncDataSourceTableViewLockDataSource:1;
|
||||
unsigned int asyncDataSourceTableViewUnlockDataSource:1;
|
||||
} _asyncDataSourceFlags;
|
||||
}
|
||||
|
||||
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
||||
@property (nonatomic, strong, readwrite) ASDataController *dataController;
|
||||
|
||||
// Used only when ASTableView is created directly rather than through ASTableNode.
|
||||
@@ -177,16 +172,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
_rangeController.dataSource = self;
|
||||
_rangeController.delegate = self;
|
||||
|
||||
_dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:NO];
|
||||
_dataController = [[dataControllerClass alloc] init];
|
||||
_dataController.dataSource = self;
|
||||
_dataController.delegate = _rangeController;
|
||||
_dataController.environmentDelegate = self;
|
||||
|
||||
_layoutController.dataSource = _dataController;
|
||||
|
||||
_asyncDataFetchingEnabled = NO;
|
||||
_asyncDataSourceLocked = NO;
|
||||
|
||||
_leadingScreensForBatching = 2.0;
|
||||
_batchContext = [[ASBatchContext alloc] init];
|
||||
|
||||
@@ -290,8 +282,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)];
|
||||
|
||||
// Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath:
|
||||
ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath || _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath);
|
||||
@@ -1083,28 +1073,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
CGSizeMake(_nodesConstrainedWidth, FLT_MAX));
|
||||
}
|
||||
|
||||
- (void)dataControllerLockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked");
|
||||
|
||||
self.asyncDataSourceLocked = YES;
|
||||
|
||||
if (_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource) {
|
||||
[_asyncDataSource tableViewLockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dataControllerUnlockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked");
|
||||
|
||||
self.asyncDataSourceLocked = NO;
|
||||
|
||||
if (_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource) {
|
||||
[_asyncDataSource tableViewUnlockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
|
||||
{
|
||||
return [_asyncDataSource tableView:self numberOfRowsInSection:section];
|
||||
|
||||
@@ -26,17 +26,6 @@
|
||||
|
||||
@implementation ASChangeSetDataController
|
||||
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
{
|
||||
if (!(self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_changeSetBatchUpdateCounter = 0;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Batching (External API)
|
||||
|
||||
- (void)beginUpdates
|
||||
@@ -66,6 +55,8 @@
|
||||
[super deleteSections:change.indexSet withAnimationOptions:change.animationOptions];
|
||||
}
|
||||
|
||||
// TODO: Shouldn't reloads be processed before deletes, since deletes affect
|
||||
// the index space and reloads don't?
|
||||
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) {
|
||||
[super reloadSections:change.indexSet withAnimationOptions:change.animationOptions];
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingContexts;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled];
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_pendingContexts = [NSMutableDictionary dictionary];
|
||||
}
|
||||
@@ -53,15 +53,13 @@
|
||||
|
||||
- (void)willReloadData
|
||||
{
|
||||
NSArray *keys = _pendingContexts.allKeys;
|
||||
for (NSString *kind in keys) {
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = _pendingContexts[kind];
|
||||
[_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, __unused BOOL * _Nonnull stop) {
|
||||
// Remove everything that existed before the reload, now that we're ready to insert replacements
|
||||
NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind];
|
||||
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
|
||||
|
||||
NSArray *editingNodes = [self editingNodesOfKind:kind];
|
||||
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
|
||||
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
|
||||
[self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil];
|
||||
|
||||
// Insert each section
|
||||
@@ -75,8 +73,8 @@
|
||||
[self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
||||
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
||||
}];
|
||||
[_pendingContexts removeObjectForKey:kind];
|
||||
}
|
||||
}];
|
||||
[_pendingContexts removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)prepareForInsertSections:(NSIndexSet *)sections
|
||||
|
||||
@@ -57,17 +57,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
|
||||
*/
|
||||
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController;
|
||||
|
||||
/**
|
||||
Lock the data source for data fetching.
|
||||
*/
|
||||
- (void)dataControllerLockDataSource;
|
||||
|
||||
/**
|
||||
Unlock the data source after data fetching.
|
||||
*/
|
||||
- (void)dataControllerUnlockDataSource;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@protocol ASDataControllerEnvironmentDelegate
|
||||
@@ -135,20 +124,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASDataControllerEnvironmentDelegate> environmentDelegate;
|
||||
|
||||
/**
|
||||
* Designated initializer.
|
||||
*
|
||||
* @param asyncDataFetchingEnabled Enable the data fetching in async mode.
|
||||
*
|
||||
* @discussion If enabled, we will fetch data through `dataController:nodeAtIndexPath:` and `dataController:rowsInSection:` in background thread.
|
||||
* Otherwise, the methods will be invoked synchronically in calling thread. Enabling data fetching in async mode could avoid blocking main thread
|
||||
* while allocating cell on main thread, which is frequently reported issue for handling large scale data. On another hand, the application code
|
||||
* will take the responsibility to avoid data inconsistency. Specifically, we will lock the data source through `dataControllerLockDataSource`,
|
||||
* and unlock it by `dataControllerUnlockDataSource` after the data fetching. The application should not update the data source while
|
||||
* the data source is locked.
|
||||
*/
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
|
||||
/** @name Data Updating */
|
||||
|
||||
- (void)beginUpdates;
|
||||
|
||||
@@ -44,8 +44,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking.
|
||||
NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes.
|
||||
|
||||
BOOL _asyncDataFetchingEnabled;
|
||||
|
||||
BOOL _initialReloadDataHasBeenCalled;
|
||||
|
||||
BOOL _delegateDidInsertNodes;
|
||||
@@ -62,7 +60,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
@@ -83,7 +81,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
_editingTransactionQueue.name = @"org.AsyncDisplayKit.ASDataController.editingTransactionQueue";
|
||||
|
||||
_batchUpdateCounter = 0;
|
||||
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -119,6 +116,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
|
||||
NSUInteger count = contexts.count;
|
||||
|
||||
@@ -143,8 +142,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize
|
||||
{
|
||||
[node measureWithSizeRange:constrainedSize];
|
||||
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
|
||||
CGSize size = [node measureWithSizeRange:constrainedSize].size;
|
||||
node.frame = { .size = size };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,6 +151,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
[self batchLayoutNodesFromContexts:contexts ofKind:ASDataControllerRowNodeKind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
||||
// Insert finished nodes into data storage
|
||||
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
@@ -163,6 +164,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_layoutNodes:(NSArray<ASCellNode *> *)nodes fromContexts:(NSArray<ASIndexedNodeContext *> *)contexts atIndexesOfRange:(NSRange)range ofKind:(NSString *)kind
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] != _editingTransactionQueue, @"%@ should not be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
if (_dataSource == nil) {
|
||||
return;
|
||||
}
|
||||
@@ -182,6 +185,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)_layoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
if (!contexts.count || _dataSource == nil) {
|
||||
return;
|
||||
}
|
||||
@@ -278,7 +283,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
NSMutableArray *editingNodes = _editingNodes[kind];
|
||||
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes);
|
||||
_editingNodes[kind] = editingNodes;
|
||||
|
||||
// Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads.
|
||||
NSMutableArray *completedNodes = ASTwoDimensionalArrayDeepMutableCopy(editingNodes);
|
||||
@@ -359,7 +363,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
[self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (_delegateDidInsertNodes)
|
||||
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
}];
|
||||
@@ -373,7 +381,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
[self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (_delegateDidDeleteNodes)
|
||||
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
}];
|
||||
@@ -387,7 +399,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
[self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (_delegateDidInsertSections)
|
||||
[_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
}];
|
||||
@@ -401,7 +417,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
[self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (_delegateDidDeleteSections)
|
||||
[_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
}];
|
||||
@@ -426,7 +446,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[self accessDataSourceSynchronously:synchronously withBlock:^{
|
||||
NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self];
|
||||
NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
|
||||
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet];
|
||||
@@ -434,7 +453,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
// Allow subclasses to perform setup before going into the edit transaction
|
||||
[self prepareForReloadData];
|
||||
|
||||
void (^transactionBlock)() = ^{
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
LOG(@"Edit Transaction - reloadData");
|
||||
|
||||
// Remove everything that existed before the reload, now that we're ready to insert replacements
|
||||
@@ -442,7 +461,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
NSUInteger editingNodesSectionCount = editingNodes.count;
|
||||
|
||||
if (editingNodesSectionCount) {
|
||||
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)];
|
||||
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)];
|
||||
[self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions];
|
||||
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
}
|
||||
@@ -461,14 +480,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
if (completion) {
|
||||
dispatch_async(dispatch_get_main_queue(), completion);
|
||||
}
|
||||
};
|
||||
|
||||
if (synchronously) {
|
||||
transactionBlock();
|
||||
} else {
|
||||
[_editingTransactionQueue addOperationWithBlock:transactionBlock];
|
||||
}
|
||||
}];
|
||||
if (synchronously) {
|
||||
[self waitUntilAllUpdatesAreCommitted];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -491,45 +506,21 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
#pragma mark - Data Source Access (Calling _dataSource)
|
||||
|
||||
/**
|
||||
* Safely locks access to the data source and executes the given block, unlocking once complete.
|
||||
*
|
||||
* @discussion When `asyncDataFetching` is enabled, the block is executed on a background thread.
|
||||
*/
|
||||
- (void)accessDataSourceWithBlock:(dispatch_block_t)block
|
||||
{
|
||||
[self accessDataSourceSynchronously:NO withBlock:block];
|
||||
}
|
||||
|
||||
- (void)accessDataSourceSynchronously:(BOOL)synchronously withBlock:(dispatch_block_t)block
|
||||
{
|
||||
if (!synchronously && _asyncDataFetchingEnabled) {
|
||||
[_dataSource dataControllerLockDataSource];
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
block();
|
||||
[_dataSource dataControllerUnlockDataSource];
|
||||
});
|
||||
} else {
|
||||
[_dataSource dataControllerLockDataSource];
|
||||
block();
|
||||
[_dataSource dataControllerUnlockDataSource];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches row contexts for the provided sections from the data source.
|
||||
*/
|
||||
- (NSArray<ASIndexedNodeContext *> *)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
||||
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
|
||||
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
|
||||
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx];
|
||||
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
|
||||
[indexSet enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL *stop) {
|
||||
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:sectionIndex];
|
||||
for (NSUInteger i = 0; i < rowNum; i++) {
|
||||
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex];
|
||||
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
|
||||
|
||||
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
|
||||
@@ -628,7 +619,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
LOG(@"Edit Command - insertSections: %@", sections);
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections];
|
||||
|
||||
[self prepareForInsertSections:sections];
|
||||
@@ -647,7 +637,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
@@ -678,7 +667,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
NSArray<ASIndexedNodeContext *> *contexts= [self _populateFromDataSourceWithSectionIndexSet:sections];
|
||||
|
||||
[self prepareForReloadSections:sections];
|
||||
@@ -696,7 +684,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
@@ -818,7 +805,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
||||
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
|
||||
|
||||
@@ -840,7 +826,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
@@ -874,8 +859,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
// Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source.
|
||||
[self accessDataSourceWithBlock:^{
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||
|
||||
// Sort indexPath to avoid messing up the index when deleting
|
||||
@@ -904,7 +887,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)relayoutAllNodes
|
||||
@@ -935,16 +917,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
return;
|
||||
}
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
[nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) {
|
||||
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
|
||||
NSUInteger sectionIndex = 0;
|
||||
for (NSMutableArray *section in nodes) {
|
||||
NSUInteger rowIndex = 0;
|
||||
for (ASCellNode *node in section) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
|
||||
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
|
||||
ASLayout *layout = [node measureWithSizeRange:constrainedSize];
|
||||
node.frame = CGRectMake(0.0f, 0.0f, layout.size.width, layout.size.height);
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
CGSize size = [node measureWithSizeRange:constrainedSize].size;
|
||||
node.frame = { .size = size };
|
||||
rowIndex += 1;
|
||||
}
|
||||
sectionIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
@@ -1022,16 +1006,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
NSArray *nodes = [self completedNodes];
|
||||
NSUInteger numberOfNodes = nodes.count;
|
||||
|
||||
NSInteger section = 0;
|
||||
// Loop through each section to look for the cellNode
|
||||
for (NSUInteger i = 0; i < numberOfNodes; i++) {
|
||||
NSArray *sectionNodes = nodes[i];
|
||||
NSUInteger cellIndex = [sectionNodes indexOfObjectIdenticalTo:cellNode];
|
||||
if (cellIndex != NSNotFound) {
|
||||
return [NSIndexPath indexPathForRow:cellIndex inSection:i];
|
||||
for (NSArray *sectionNodes in [self completedNodes]) {
|
||||
NSUInteger item = [sectionNodes indexOfObjectIdenticalTo:cellNode];
|
||||
if (item != NSNotFound) {
|
||||
return [NSIndexPath indexPathForItem:item inSection:section];
|
||||
}
|
||||
section += 1;
|
||||
}
|
||||
|
||||
return nil;
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#import "ASAssert.h"
|
||||
#import "ASMultidimensionalArrayUtils.h"
|
||||
|
||||
// Import UIKit to get [NSIndexPath indexPathForItem:inSection:] which uses
|
||||
// static memory addresses rather than allocating new index path objects.
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#pragma mark - Internal Methods
|
||||
|
||||
static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray,
|
||||
@@ -25,8 +29,10 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray
|
||||
}
|
||||
|
||||
if (curIndexPath.length < dimension - 1) {
|
||||
for (int i = 0; i < mutableArray.count; i++) {
|
||||
ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray[i], indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock);
|
||||
NSInteger i = 0;
|
||||
for (NSMutableArray *subarray in mutableArray) {
|
||||
ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(subarray, indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock);
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
|
||||
@@ -72,7 +78,12 @@ static BOOL ASElementExistsAtIndexPathForMultidimensionalArray(NSArray *array, N
|
||||
NSUInteger indexesLength = indexLength - 1;
|
||||
NSUInteger indexes[indexesLength];
|
||||
[indexPath getIndexes:indexes range:NSMakeRange(1, indexesLength)];
|
||||
NSIndexPath *indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength];
|
||||
NSIndexPath *indexPathByRemovingFirstIndex;
|
||||
if (indexesLength == 2) {
|
||||
indexPathByRemovingFirstIndex = [NSIndexPath indexPathForItem:indexes[1] inSection:indexes[0]];
|
||||
} else {
|
||||
indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength];
|
||||
}
|
||||
|
||||
return ASElementExistsAtIndexPathForMultidimensionalArray(array[firstIndex], indexPathByRemovingFirstIndex);
|
||||
}
|
||||
@@ -184,9 +195,8 @@ NSArray *ASIndexPathsForTwoDimensionalArray(NSArray <NSArray *>* twoDimensionalA
|
||||
NSUInteger section = 0;
|
||||
for (NSArray *subarray in twoDimensionalArray) {
|
||||
ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray<NSArray *> *");
|
||||
NSUInteger itemCount = subarray.count;
|
||||
for (NSUInteger item = 0; item < itemCount; item++) {
|
||||
[result addObject:[NSIndexPath indexPathWithIndexes:(const NSUInteger []){ section, item } length:2]];
|
||||
for (NSUInteger item = 0; item < subarray.count; item++) {
|
||||
[result addObject:[NSIndexPath indexPathForItem:item inSection:section]];
|
||||
}
|
||||
section++;
|
||||
}
|
||||
|
||||
@@ -408,8 +408,7 @@
|
||||
{
|
||||
CGSize tableViewSize = CGSizeMake(100, 500);
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
|
||||
style:UITableViewStylePlain
|
||||
asyncDataFetching:YES];
|
||||
style:UITableViewStylePlain];
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
tableView.asyncDelegate = dataSource;
|
||||
|
||||
Reference in New Issue
Block a user