[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:
Adlai Holler
2016-06-23 21:08:44 -07:00
committed by appleguy
parent 997d37dc83
commit 457e08005f
10 changed files with 192 additions and 299 deletions

View File

@@ -407,8 +407,9 @@ NS_ASSUME_NONNULL_BEGIN
* due to the data access in async mode. * due to the data access in async mode.
* *
* @param collectionView The sender. * @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. * 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. * due to the data access in async mode.
* *
* @param collectionView The sender. * @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 @end

View File

@@ -112,7 +112,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
BOOL _performingBatchUpdates; BOOL _performingBatchUpdates;
NSMutableArray *_batchUpdateBlocks; NSMutableArray *_batchUpdateBlocks;
BOOL _asyncDataFetchingEnabled;
_ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only _ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only
BOOL _isDeallocating; BOOL _isDeallocating;
@@ -155,14 +154,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
unsigned int asyncDataSourceNodeForItemAtIndexPath:1; unsigned int asyncDataSourceNodeForItemAtIndexPath:1;
unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1; unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1;
unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1; unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1;
unsigned int asyncDataSourceCollectionViewLockDataSource:1;
unsigned int asyncDataSourceCollectionViewUnlockDataSource:1;
unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1; unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1;
} _asyncDataSourceFlags; } _asyncDataSourceFlags;
} }
@property (atomic, assign) BOOL asyncDataSourceLocked;
// Used only when ASCollectionView is created directly rather than through ASCollectionNode. // 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 // 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. // 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.delegate = self;
_rangeController.layoutController = _layoutController; _rangeController.layoutController = _layoutController;
_dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:NO]; _dataController = [[ASCollectionDataController alloc] init];
_dataController.delegate = _rangeController; _dataController.delegate = _rangeController;
_dataController.dataSource = self; _dataController.dataSource = self;
_dataController.environmentDelegate = self; _dataController.environmentDelegate = self;
@@ -240,9 +235,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
_leadingScreensForBatching = 2.0; _leadingScreensForBatching = 2.0;
_asyncDataFetchingEnabled = NO;
_asyncDataSourceLocked = NO;
_performingBatchUpdates = NO; _performingBatchUpdates = NO;
_batchUpdateBlocks = [NSMutableArray array]; _batchUpdateBlocks = [NSMutableArray array];
@@ -375,9 +367,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
_asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)]; _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)];
_asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];;
// Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath:
ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath); 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 - (id<ASEnvironment>)dataControllerEnvironment
{ {
if (self.collectionNode) { if (self.collectionNode) {

View File

@@ -44,18 +44,8 @@ NS_ASSUME_NONNULL_BEGIN
* The frame of the table view changes as table cells are added and deleted. * 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 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. * 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. * due to the data access in async mode.
* *
* @param tableView The sender. * @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. * 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. * due to the data access in async mode.
* *
* @param tableView The sender. * @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 @end
@@ -458,4 +450,10 @@ NS_ASSUME_NONNULL_BEGIN
@protocol ASTableViewDelegate <ASTableDelegate> @protocol ASTableViewDelegate <ASTableDelegate>
@end @end
@interface ASTableView (Deprecated)
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled ASDISPLAYNODE_DEPRECATED;
@end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -100,8 +100,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
ASRangeController *_rangeController; ASRangeController *_rangeController;
BOOL _asyncDataFetchingEnabled;
ASBatchContext *_batchContext; ASBatchContext *_batchContext;
NSIndexPath *_pendingVisibleIndexPath; NSIndexPath *_pendingVisibleIndexPath;
@@ -133,12 +131,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
unsigned int asyncDataSourceNumberOfSectionsInTableView:1; unsigned int asyncDataSourceNumberOfSectionsInTableView:1;
unsigned int asyncDataSourceTableViewNodeBlockForRowAtIndexPath:1; unsigned int asyncDataSourceTableViewNodeBlockForRowAtIndexPath:1;
unsigned int asyncDataSourceTableViewNodeForRowAtIndexPath:1; unsigned int asyncDataSourceTableViewNodeForRowAtIndexPath:1;
unsigned int asyncDataSourceTableViewLockDataSource:1;
unsigned int asyncDataSourceTableViewUnlockDataSource:1;
} _asyncDataSourceFlags; } _asyncDataSourceFlags;
} }
@property (atomic, assign) BOOL asyncDataSourceLocked;
@property (nonatomic, strong, readwrite) ASDataController *dataController; @property (nonatomic, strong, readwrite) ASDataController *dataController;
// Used only when ASTableView is created directly rather than through ASTableNode. // Used only when ASTableView is created directly rather than through ASTableNode.
@@ -177,16 +172,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
_rangeController.dataSource = self; _rangeController.dataSource = self;
_rangeController.delegate = self; _rangeController.delegate = self;
_dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:NO]; _dataController = [[dataControllerClass alloc] init];
_dataController.dataSource = self; _dataController.dataSource = self;
_dataController.delegate = _rangeController; _dataController.delegate = _rangeController;
_dataController.environmentDelegate = self; _dataController.environmentDelegate = self;
_layoutController.dataSource = _dataController; _layoutController.dataSource = _dataController;
_asyncDataFetchingEnabled = NO;
_asyncDataSourceLocked = NO;
_leadingScreensForBatching = 2.0; _leadingScreensForBatching = 2.0;
_batchContext = [[ASBatchContext alloc] init]; _batchContext = [[ASBatchContext alloc] init];
@@ -290,8 +282,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)];
_asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)];
_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; _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: // Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath:
ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath || _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath); ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath || _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath);
@@ -1083,28 +1073,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
CGSizeMake(_nodesConstrainedWidth, FLT_MAX)); 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 - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
{ {
return [_asyncDataSource tableView:self numberOfRowsInSection:section]; return [_asyncDataSource tableView:self numberOfRowsInSection:section];

View File

@@ -26,17 +26,6 @@
@implementation ASChangeSetDataController @implementation ASChangeSetDataController
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
{
if (!(self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled])) {
return nil;
}
_changeSetBatchUpdateCounter = 0;
return self;
}
#pragma mark - Batching (External API) #pragma mark - Batching (External API)
- (void)beginUpdates - (void)beginUpdates
@@ -66,6 +55,8 @@
[super deleteSections:change.indexSet withAnimationOptions:change.animationOptions]; [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]) { for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) {
[super reloadSections:change.indexSet withAnimationOptions:change.animationOptions]; [super reloadSections:change.indexSet withAnimationOptions:change.animationOptions];
} }

View File

@@ -32,9 +32,9 @@
NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingContexts; NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingContexts;
} }
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled - (instancetype)init
{ {
self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled]; self = [super init];
if (self != nil) { if (self != nil) {
_pendingContexts = [NSMutableDictionary dictionary]; _pendingContexts = [NSMutableDictionary dictionary];
} }
@@ -53,15 +53,13 @@
- (void)willReloadData - (void)willReloadData
{ {
NSArray *keys = _pendingContexts.allKeys; [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, __unused BOOL * _Nonnull stop) {
for (NSString *kind in keys) {
NSMutableArray<ASIndexedNodeContext *> *contexts = _pendingContexts[kind];
// Remove everything that existed before the reload, now that we're ready to insert replacements // Remove everything that existed before the reload, now that we're ready to insert replacements
NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind];
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
NSArray *editingNodes = [self editingNodesOfKind:kind]; 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]; [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil];
// Insert each section // Insert each section
@@ -75,8 +73,8 @@
[self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) { [self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}]; }];
[_pendingContexts removeObjectForKey:kind]; }];
} [_pendingContexts removeAllObjects];
} }
- (void)prepareForInsertSections:(NSIndexSet *)sections - (void)prepareForInsertSections:(NSIndexSet *)sections

View File

@@ -57,17 +57,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
*/ */
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController; - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController;
/**
Lock the data source for data fetching.
*/
- (void)dataControllerLockDataSource;
/**
Unlock the data source after data fetching.
*/
- (void)dataControllerUnlockDataSource;
@end @end
@protocol ASDataControllerEnvironmentDelegate @protocol ASDataControllerEnvironmentDelegate
@@ -135,20 +124,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
*/ */
@property (nonatomic, weak) id<ASDataControllerEnvironmentDelegate> environmentDelegate; @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 */ /** @name Data Updating */
- (void)beginUpdates; - (void)beginUpdates;

View File

@@ -44,8 +44,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking. NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking.
NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes.
BOOL _asyncDataFetchingEnabled;
BOOL _initialReloadDataHasBeenCalled; BOOL _initialReloadDataHasBeenCalled;
BOOL _delegateDidInsertNodes; BOOL _delegateDidInsertNodes;
@@ -62,7 +60,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Lifecycle #pragma mark - Lifecycle
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled - (instancetype)init
{ {
if (!(self = [super init])) { if (!(self = [super init])) {
return nil; return nil;
@@ -83,7 +81,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
_editingTransactionQueue.name = @"org.AsyncDisplayKit.ASDataController.editingTransactionQueue"; _editingTransactionQueue.name = @"org.AsyncDisplayKit.ASDataController.editingTransactionQueue";
_batchUpdateCounter = 0; _batchUpdateCounter = 0;
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
return self; return self;
} }
@@ -119,6 +116,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock - (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 blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
NSUInteger count = contexts.count; NSUInteger count = contexts.count;
@@ -143,8 +142,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
*/ */
- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize - (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize
{ {
[node measureWithSizeRange:constrainedSize]; CGSize size = [node measureWithSizeRange:constrainedSize].size;
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); node.frame = { .size = size };
} }
/** /**
@@ -152,6 +151,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
*/ */
- (void)_batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (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) { [self batchLayoutNodesFromContexts:contexts ofKind:ASDataControllerRowNodeKind completion:^(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];
@@ -163,6 +164,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
*/ */
- (void)_layoutNodes:(NSArray<ASCellNode *> *)nodes fromContexts:(NSArray<ASIndexedNodeContext *> *)contexts atIndexesOfRange:(NSRange)range ofKind:(NSString *)kind - (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) { if (_dataSource == nil) {
return; return;
} }
@@ -182,6 +185,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)_layoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock - (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) { if (!contexts.count || _dataSource == nil) {
return; return;
} }
@@ -278,7 +283,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
NSMutableArray *editingNodes = _editingNodes[kind]; NSMutableArray *editingNodes = _editingNodes[kind];
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); 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. // 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); NSMutableArray *completedNodes = ASTwoDimensionalArrayDeepMutableCopy(editingNodes);
@@ -359,7 +363,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
*/ */
- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (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) { [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
ASDisplayNodeAssertMainThread();
if (_delegateDidInsertNodes) if (_delegateDidInsertNodes)
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}]; }];
@@ -373,7 +381,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
*/ */
- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (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) { [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
ASDisplayNodeAssertMainThread();
if (_delegateDidDeleteNodes) if (_delegateDidDeleteNodes)
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; [_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 - (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) { [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) {
ASDisplayNodeAssertMainThread();
if (_delegateDidInsertSections) if (_delegateDidInsertSections)
[_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions];
}]; }];
@@ -401,7 +417,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
*/ */
- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (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) { [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) {
ASDisplayNodeAssertMainThread();
if (_delegateDidDeleteSections) if (_delegateDidDeleteSections)
[_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
}]; }];
@@ -426,49 +446,44 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_editingTransactionQueue waitUntilAllOperationsAreFinished]; [_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceSynchronously:synchronously withBlock:^{ NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self];
NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; 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];
// 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];
[_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - reloadData");
void (^transactionBlock)() = ^{ // Remove everything that existed before the reload, now that we're ready to insert replacements
LOG(@"Edit Transaction - reloadData"); NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
NSUInteger editingNodesSectionCount = editingNodes.count;
// Remove everything that existed before the reload, now that we're ready to insert replacements
NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
NSUInteger editingNodesSectionCount = editingNodes.count;
if (editingNodesSectionCount) {
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)];
[self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
}
[self willReloadData];
// Insert empty sections
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]];
}
[self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions];
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
if (completion) {
dispatch_async(dispatch_get_main_queue(), completion);
}
};
if (synchronously) { if (editingNodesSectionCount) {
transactionBlock(); NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)];
} else { [self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions];
[_editingTransactionQueue addOperationWithBlock:transactionBlock]; [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
}
[self willReloadData];
// Insert empty sections
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]];
}
[self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions];
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
if (completion) {
dispatch_async(dispatch_get_main_queue(), completion);
} }
}]; }];
if (synchronously) {
[self waitUntilAllUpdatesAreCommitted];
}
}]; }];
} }
@@ -491,45 +506,21 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Data Source Access (Calling _dataSource) #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. * Fetches row contexts for the provided sections from the data source.
*/ */
- (NSArray<ASIndexedNodeContext *> *)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet - (NSArray<ASIndexedNodeContext *> *)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet
{ {
ASDisplayNodeAssertMainThread();
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment]; id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array]; NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { [indexSet enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL *stop) {
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx]; NSUInteger rowNum = [_dataSource dataController:self rowsInSection:sectionIndex];
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
for (NSUInteger i = 0; i < rowNum; i++) { 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]; ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
@@ -628,24 +619,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
LOG(@"Edit Command - insertSections: %@", sections); LOG(@"Edit Command - insertSections: %@", sections);
[_editingTransactionQueue waitUntilAllOperationsAreFinished]; [_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{ NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections];
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections];
[self prepareForInsertSections:sections]; [self prepareForInsertSections:sections];
[_editingTransactionQueue addOperationWithBlock:^{
[self willInsertSections:sections];
LOG(@"Edit Transaction - insertSections: %@", sections);
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
[_editingTransactionQueue addOperationWithBlock:^{ [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
[self willInsertSections:sections];
LOG(@"Edit Transaction - insertSections: %@", sections);
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
}];
}]; }];
}]; }];
} }
@@ -678,23 +667,21 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished]; [_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{ NSArray<ASIndexedNodeContext *> *contexts= [self _populateFromDataSourceWithSectionIndexSet:sections];
NSArray<ASIndexedNodeContext *> *contexts= [self _populateFromDataSourceWithSectionIndexSet:sections];
[self prepareForReloadSections:sections]; [self prepareForReloadSections:sections];
[_editingTransactionQueue addOperationWithBlock:^{
[self willReloadSections:sections];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
[_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForTwoDimensionalArray(_editingNodes[ASDataControllerRowNodeKind]));
[self willReloadSections:sections];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); // reinsert the elements
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForTwoDimensionalArray(_editingNodes[ASDataControllerRowNodeKind]));
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// reinsert the elements
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
}];
}]; }];
}]; }];
} }
@@ -818,27 +805,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
[self accessDataSourceWithBlock:^{ id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment]; ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
for (NSIndexPath *indexPath in sortedIndexPaths) {
for (NSIndexPath *indexPath in sortedIndexPaths) { ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock indexPath:indexPath
indexPath:indexPath constrainedSize:constrainedSize
constrainedSize:constrainedSize environmentTraitCollection:environmentTraitCollection]];
environmentTraitCollection:environmentTraitCollection]]; }
}
[self prepareForInsertRowsAtIndexPaths:indexPaths]; [self prepareForInsertRowsAtIndexPaths:indexPaths];
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
[self willInsertRowsAtIndexPaths:indexPaths]; [self willInsertRowsAtIndexPaths:indexPaths];
LOG(@"Edit Transaction - insertRows: %@", indexPaths); LOG(@"Edit Transaction - insertRows: %@", indexPaths);
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
}];
}]; }];
}]; }];
} }
@@ -874,35 +859,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished]; [_editingTransactionQueue waitUntilAllOperationsAreFinished];
// Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
[self accessDataSourceWithBlock:^{
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; // Sort indexPath to avoid messing up the index when deleting
// FIXME: Shouldn't deletes be sorted in descending order?
// Sort indexPath to avoid messing up the index when deleting NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
// FIXME: Shouldn't deletes be sorted in descending order?
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; for (NSIndexPath *indexPath in sortedIndexPaths) {
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
for (NSIndexPath *indexPath in sortedIndexPaths) { ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; indexPath:indexPath
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock constrainedSize:constrainedSize
indexPath:indexPath environmentTraitCollection:environmentTraitCollection]];
constrainedSize:constrainedSize }
environmentTraitCollection:environmentTraitCollection]];
}
[self prepareForReloadRowsAtIndexPaths:indexPaths]; [self prepareForReloadRowsAtIndexPaths:indexPaths];
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
[self willReloadRowsAtIndexPaths:indexPaths]; [self willReloadRowsAtIndexPaths:indexPaths];
LOG(@"Edit Transaction - reloadRows: %@", indexPaths); LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions]; [self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
}];
}]; }];
}]; }];
} }
@@ -935,16 +917,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
return; return;
} }
[self accessDataSourceWithBlock:^{ NSUInteger sectionIndex = 0;
[nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) { for (NSMutableArray *section in nodes) {
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) { NSUInteger rowIndex = 0;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; for (ASCellNode *node in section) {
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
ASLayout *layout = [node measureWithSizeRange:constrainedSize]; ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
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 - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
@@ -1021,17 +1005,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode;
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
NSArray *nodes = [self completedNodes];
NSUInteger numberOfNodes = nodes.count;
NSInteger section = 0;
// Loop through each section to look for the cellNode // Loop through each section to look for the cellNode
for (NSUInteger i = 0; i < numberOfNodes; i++) { for (NSArray *sectionNodes in [self completedNodes]) {
NSArray *sectionNodes = nodes[i]; NSUInteger item = [sectionNodes indexOfObjectIdenticalTo:cellNode];
NSUInteger cellIndex = [sectionNodes indexOfObjectIdenticalTo:cellNode]; if (item != NSNotFound) {
if (cellIndex != NSNotFound) { return [NSIndexPath indexPathForItem:item inSection:section];
return [NSIndexPath indexPathForRow:cellIndex inSection:i];
} }
section += 1;
} }
return nil; return nil;

View File

@@ -11,6 +11,10 @@
#import "ASAssert.h" #import "ASAssert.h"
#import "ASMultidimensionalArrayUtils.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 #pragma mark - Internal Methods
static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray,
@@ -25,8 +29,10 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray
} }
if (curIndexPath.length < dimension - 1) { if (curIndexPath.length < dimension - 1) {
for (int i = 0; i < mutableArray.count; i++) { NSInteger i = 0;
ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray[i], indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock); for (NSMutableArray *subarray in mutableArray) {
ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(subarray, indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock);
i += 1;
} }
} else { } else {
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
@@ -72,7 +78,12 @@ static BOOL ASElementExistsAtIndexPathForMultidimensionalArray(NSArray *array, N
NSUInteger indexesLength = indexLength - 1; NSUInteger indexesLength = indexLength - 1;
NSUInteger indexes[indexesLength]; NSUInteger indexes[indexesLength];
[indexPath getIndexes:indexes range:NSMakeRange(1, 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); return ASElementExistsAtIndexPathForMultidimensionalArray(array[firstIndex], indexPathByRemovingFirstIndex);
} }
@@ -184,9 +195,8 @@ NSArray *ASIndexPathsForTwoDimensionalArray(NSArray <NSArray *>* twoDimensionalA
NSUInteger section = 0; NSUInteger section = 0;
for (NSArray *subarray in twoDimensionalArray) { for (NSArray *subarray in twoDimensionalArray) {
ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray<NSArray *> *"); ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray<NSArray *> *");
NSUInteger itemCount = subarray.count; for (NSUInteger item = 0; item < subarray.count; item++) {
for (NSUInteger item = 0; item < itemCount; item++) { [result addObject:[NSIndexPath indexPathForItem:item inSection:section]];
[result addObject:[NSIndexPath indexPathWithIndexes:(const NSUInteger []){ section, item } length:2]];
} }
section++; section++;
} }

View File

@@ -408,8 +408,7 @@
{ {
CGSize tableViewSize = CGSizeMake(100, 500); CGSize tableViewSize = CGSizeMake(100, 500);
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
style:UITableViewStylePlain style:UITableViewStylePlain];
asyncDataFetching:YES];
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
tableView.asyncDelegate = dataSource; tableView.asyncDelegate = dataSource;