Merge pull request #1171 from lkzhao/ASDataController-reload

[ASCollectionView / ASTableView] Optimize reloadData and reloadSection: methods.
This commit is contained in:
appleguy 2016-02-18 17:36:14 -08:00
commit 33d4c8693d
10 changed files with 478 additions and 346 deletions

View File

@ -647,7 +647,7 @@
205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = "<group>"; }; 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = "<group>"; };
205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = "<group>"; }; 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = "<group>"; };
242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = "<group>"; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = "<group>"; };
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = "<group>"; };
251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = "<group>"; }; 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = "<group>"; };
251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = "<group>"; }; 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = "<group>"; };
251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = "<group>"; }; 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = "<group>"; };

View File

@ -249,7 +249,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
_superIsPendingDataLoad = YES; _superIsPendingDataLoad = YES;
[super reloadData]; [super reloadData];
}); });
[_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion]; [_dataController reloadDataWithCompletion:completion];
} }
- (void)reloadData - (void)reloadData
@ -261,7 +261,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
_superIsPendingDataLoad = YES; _superIsPendingDataLoad = YES;
[_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone]; [_dataController reloadDataImmediately];
[super reloadData]; [super reloadData];
} }
@ -446,7 +446,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; [_dataController moveSection:section toSection:newSection];
} }
- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
@ -470,7 +470,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
} }
- (NSString *)__reuseIdentifierForKind:(NSString *)kind - (NSString *)__reuseIdentifierForKind:(NSString *)kind
@ -951,6 +951,46 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
} }
} }
- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:YES];
[_batchUpdateBlocks addObject:^{
[super reloadItemsAtIndexPaths:indexPaths];
}];
} else {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
[UIView performWithoutAnimation:^{
[super reloadItemsAtIndexPaths:indexPaths];
}];
}
}
- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:YES];
[_batchUpdateBlocks addObject:^{
[super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
}];
} else {
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:@[fromIndexPath] batched:NO];
[UIView performWithoutAnimation:^{
[super moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
}];
}
}
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -971,6 +1011,26 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
} }
} }
- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:YES];
[_batchUpdateBlocks addObject:^{
[super reloadSections:indexSet];
}];
} else {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO];
[UIView performWithoutAnimation:^{
[super reloadSections:indexSet];
}];
}
}
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -991,6 +1051,43 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
} }
} }
- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_performingBatchUpdates) {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:YES];
[_batchUpdateBlocks addObject:^{
[super moveSection:fromIndex toSection:toIndex];
}];
} else {
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:[NSIndexSet indexSetWithIndex:fromIndex] batched:NO];
[UIView performWithoutAnimation:^{
[super moveSection:fromIndex toSection:toIndex];
}];
}
}
- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource || _superIsPendingDataLoad) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
if (_performingBatchUpdates) {
[_batchUpdateBlocks addObject:^{
[super reloadData];
}];
} else {
[UIView performWithoutAnimation:^{
[super reloadData];
}];
}
}
#pragma mark - ASCellNodeDelegate #pragma mark - ASCellNodeDelegate
- (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged

View File

@ -303,10 +303,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)reloadDataWithCompletion:(void (^)())completion - (void)reloadDataWithCompletion:(void (^)())completion
{ {
ASPerformBlockOnMainThread(^{ [_dataController reloadDataWithCompletion:completion];
[super reloadData];
});
[_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion];
} }
- (void)reloadData - (void)reloadData
@ -317,8 +314,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)reloadDataImmediately - (void)reloadDataImmediately
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone]; [_dataController reloadDataImmediately];
[super reloadData];
} }
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
@ -433,7 +429,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; [_dataController moveSection:section toSection:newSection];
} }
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
@ -457,7 +453,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
[_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
} }
#pragma mark - #pragma mark -
@ -836,6 +832,36 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
} }
} }
- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
LOG(@"UITableView reloadRows:%ld rows", indexPaths.count);
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
ASPerformBlockWithoutAnimation(preventAnimation, ^{
[super reloadRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions];
});
if (_automaticallyAdjustsContentOffset) {
[self adjustContentOffsetWithNodes:nodes atIndexPaths:indexPaths inserting:YES];
}
}
- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
ASDisplayNodeAssertMainThread();
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
[self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
}
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -852,6 +878,36 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
}); });
} }
- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASDisplayNodeAssertMainThread();
LOG(@"UITableView reloadSections:%@", indexSet);
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
ASPerformBlockWithoutAnimation(preventAnimation, ^{
[super reloadSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions];
});
}
- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex
{
ASDisplayNodeAssertMainThread();
LOG(@"UITableView moveSection:%@", indexSet);
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
[super moveSection:fromIndex toSection:toIndex];
}
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -867,6 +923,17 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
}); });
} }
- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController{
ASDisplayNodeAssertMainThread();
LOG(@"UITableView reloadData");
if (!self.asyncDataSource) {
return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes
}
[super reloadData];
}
#pragma mark - ASDataControllerDelegate #pragma mark - ASDataControllerDelegate
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath {

View File

@ -131,7 +131,7 @@
[_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions];
[_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions];
} else { } else {
[super moveSection:section toSection:newSection withAnimationOptions:animationOptions]; [super moveSection:section toSection:newSection];
} }
} }
@ -174,7 +174,7 @@
[_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions];
[_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions];
} else { } else {
[super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions]; [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
} }
} }

View File

@ -49,37 +49,27 @@
[self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes; _pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths; _pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
} }
} }
- (void)willReloadData - (void)willReloadData
{ {
[_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
// Remove everything that existed before the reload, now that we're ready to insert replacements // Insert sections
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)];
[self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil];
// Insert each section
NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind];
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) { for (int i = 0; i < sectionCount; i++) {
[sections addObject:[NSMutableArray array]]; [sections addObject:[NSMutableArray array]];
} }
[self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; self.editingNode[kind] = sections;
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; [self commitChangesToNodesOfKind:kind withCompletion:nil];
}]; }];
[_pendingNodes removeObjectForKey:kind];
[_pendingIndexPaths removeObjectForKey:kind];
}]; }];
[_pendingNodes removeAllObjects];
[_pendingIndexPaths removeAllObjects];
} }
- (void)prepareForInsertSections:(NSIndexSet *)sections - (void)prepareForInsertSections:(NSIndexSet *)sections
@ -91,9 +81,6 @@
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes; _pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths; _pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
} }
} }
@ -104,23 +91,22 @@
for (NSUInteger i = 0; i < sections.count; i++) { for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]]; [sectionArray addObject:[NSMutableArray array]];
} }
[self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; [self insertSections:sectionArray ofKind:kind atIndexSet:sections];
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; [self commitChangesToNodesOfKind:kind withCompletion:nil];
}]; }];
[_pendingNodes removeObjectForKey:kind];
[_pendingIndexPaths removeObjectForKey:kind];
}]; }];
[_pendingNodes removeAllObjects];
[_pendingIndexPaths removeAllObjects];
} }
- (void)willDeleteSections:(NSIndexSet *)sections - (void)willDeleteSections:(NSIndexSet *)sections
{ {
for (NSString *kind in [self supplementaryKinds]) { for (NSString *kind in [self supplementaryKinds]) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); [self deleteSectionsOfKind:kind atIndexSet:sections];
[self commitChangesToNodesOfKind:kind withCompletion:nil];
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
[self deleteSectionsOfKind:kind atIndexSet:sections completion:nil];
} }
} }
@ -132,40 +118,31 @@
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths];
_pendingNodes[kind] = nodes; _pendingNodes[kind] = nodes;
_pendingIndexPaths[kind] = indexPaths; _pendingIndexPaths[kind] = indexPaths;
// Measure loaded nodes before leaving the main thread
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
} }
} }
- (void)willReloadSections:(NSIndexSet *)sections - (void)willReloadSections:(NSIndexSet *)sections
{ {
[_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); // clear sections
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
// reinsert the elements self.editingNode[kind][idx] = [[NSMutableArray alloc] init];
[self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { }];
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; // reinsert the elements
[self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:kind withCompletion:nil];
}]; }];
[_pendingNodes removeObjectForKey:kind];
[_pendingIndexPaths removeObjectForKey:kind];
}]; }];
[_pendingNodes removeAllObjects];
[_pendingIndexPaths removeAllObjects];
} }
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
{ {
for (NSString *kind in [self supplementaryKinds]) { for (NSString *kind in [self supplementaryKinds]) {
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); [self moveSection:section ofKind:kind toSection:newSection];
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); [self commitChangesToNodesOfKind:kind withCompletion:nil];
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
// update the section of indexpaths
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection];
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
[indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
[updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]];
}];
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
} }
} }

View File

@ -14,16 +14,7 @@
@interface ASDataController (Subclasses) @interface ASDataController (Subclasses)
#pragma mark - Internal editing & completed store querying #pragma mark - Internal editing & completed store querying
@property (nonatomic, strong, readonly) NSMutableDictionary *editingNode;
/**
* Provides a collection of index paths for nodes of the given kind that are currently in the editing store
*/
- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind;
/**
* Read-only access to the underlying editing nodes of the given kind
*/
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind;
/** /**
* Read only access to the underlying completed nodes of the given kind * Read only access to the underlying completed nodes of the given kind
@ -35,7 +26,7 @@
/** /**
* Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`.
*/ */
- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; - (void)layoutAndInsertFromNodeBlocks:(NSArray<ASCellNodeBlock> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths completion:(void (^)(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths))completionBlock;
/* /*
* Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes. * Perform measurement and layout of loaded nodes on the main thread, skipping unloaded nodes.
@ -53,24 +44,34 @@
#pragma mark - Node & Section Insertion/Deletion API #pragma mark - Node & Section Insertion/Deletion API
/** /**
* Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. * Inserts the given nodes of the specified kind into the backing store.
*/ */
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; - (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths;
/** /**
* Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. * Deletes the given nodes of the specified kind in the backing store.
*/ */
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; - (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths;
/** /**
* Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. * Inserts the given sections of the specified kind in the backing store.
*/ */
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock; - (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet;
/** /**
* Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished. * Deletes the given sections of the specified kind in the backing store.
*/ */
- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock; - (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet;
/**
* Moves the given section of the specified kind in the backing store.
*/
- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection;
/**
* Commit the change for insert/delete node or sections to the backing store, calling completion on the main thread when finished.
*/
- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock;
#pragma mark - Data Manipulation Hooks #pragma mark - Data Manipulation Hooks

View File

@ -91,16 +91,41 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
*/ */
- (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
Called for reload of elements.
*/
- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
Called for movement of elements.
*/
- (void)dataController:(ASDataController *)dataController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath;
/** /**
Called for insertion of sections. Called for insertion of sections.
*/ */
- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray<NSArray<ASCellNode *> *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/** /**
Called for deletion of sections. Called for deletion of sections.
*/ */
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
Called for reload of sections.
*/
- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
Called for movement of sections.
*/
- (void)dataController:(ASDataController *)dataController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex;
/**
Called for reload data.
*/
- (void)dataControllerDidReloadData:(ASDataController *)dataController;
@end @end
/** /**
@ -137,14 +162,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
*/ */
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled; - (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled;
/** @name Initial loading
*
* @discussion This method allows choosing an animation style for the first load of content. It is typically used just once,
* for example in viewWillAppear:, to specify an animation option for the information already present in the asyncDataSource.
*/
- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/** @name Data Updating */ /** @name Data Updating */
- (void)beginUpdates; - (void)beginUpdates;
@ -159,7 +176,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
@ -175,11 +192,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
*/ */
- (void)relayoutAllNodes; - (void)relayoutAllNodes;
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion; - (void)reloadDataWithCompletion:(void (^)())completion;
- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)reloadDataImmediately;
/** @name Data Querying */ /** @name Data Querying */

View File

@ -30,7 +30,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
static void *kASSizingQueueContext = &kASSizingQueueContext; static void *kASSizingQueueContext = &kASSizingQueueContext;
@interface ASDataController () { @interface ASDataController () {
NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available.
NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable.
NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes.
@ -42,9 +41,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
BOOL _asyncDataFetchingEnabled; BOOL _asyncDataFetchingEnabled;
BOOL _delegateDidInsertNodes; BOOL _delegateDidInsertNodes;
BOOL _delegateDidReloadNodes;
BOOL _delegateDidDeleteNodes; BOOL _delegateDidDeleteNodes;
BOOL _delegateDidMoveNode;
BOOL _delegateDidInsertSections; BOOL _delegateDidInsertSections;
BOOL _delegateDidDeleteSections; BOOL _delegateDidDeleteSections;
BOOL _delegateDidReloadSections;
BOOL _delegateDidMoveSection;
BOOL _delegateDidReloadData;
} }
@property (atomic, assign) NSUInteger batchUpdateCounter; @property (atomic, assign) NSUInteger batchUpdateCounter;
@ -92,8 +96,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later. // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later.
_delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)];
_delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)]; _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)];
_delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; _delegateDidReloadNodes = [_delegate respondsToSelector:@selector(dataController:didReloadNodes:atIndexPaths:withAnimationOptions:)];
_delegateDidMoveNode = [_delegate respondsToSelector:@selector(dataController:didMoveNodeAtIndexPath:toIndexPath:)];
_delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSectionsAtIndexSet:withAnimationOptions:)];
_delegateDidReloadSections = [_delegate respondsToSelector:@selector(dataController:didReloadSectionsAtIndexSet:withAnimationOptions:)];
_delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)];
_delegateDidMoveSection = [_delegate respondsToSelector:@selector(dataController:didMoveSection:toSection:)];
_delegateDidReloadData = [_delegate respondsToSelector:@selector(dataControllerDidReloadData:)];
} }
+ (NSUInteger)parallelProcessorCount + (NSUInteger)parallelProcessorCount
@ -110,17 +119,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - Cell Layout #pragma mark - Cell Layout
- (void)batchLayoutNodes:(NSArray<ASCellNodeBlock> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths completion:(void (^)(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths))completionBlock - (void)layoutAndInsertFromNodeBlocks:(NSArray<ASCellNodeBlock> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths completion:(void (^)(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths))completionBlock
{ {
NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; [self _layoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths];
// Processing in batches if (completionBlock) {
for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { completionBlock(nodes, indexPaths);
NSRange batchedRange = NSMakeRange(i, MIN(indexPaths.count - i, blockSize)); }
NSArray *batchedIndexPaths = [indexPaths subarrayWithRange:batchedRange]; }];
NSArray *batchedNodes = [nodes subarrayWithRange:batchedRange];
[self _layoutNodes:batchedNodes ofKind:kind atIndexPaths:batchedIndexPaths completion:completionBlock];
}
} }
- (void)layoutLoadedNodes:(NSArray<ASCellNode *> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths { - (void)layoutLoadedNodes:(NSArray<ASCellNode *> *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths {
@ -144,21 +150,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
} }
/**
* Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store.
*/
- (void)_batchLayoutNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self batchLayoutNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
// Insert finished nodes into data storage
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}
- (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock - (void)_layoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock
{ {
if (!nodes.count) { if (!nodes.count) {
return; if (completionBlock) {
completionBlock(nodes, indexPaths);
}
return;
} }
NSUInteger nodeCount = nodes.count; NSUInteger nodeCount = nodes.count;
@ -238,178 +236,84 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
#pragma mark - External Data Querying + Editing #pragma mark - External Data Querying + Editing
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock - (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths
{ {
if (indexPaths.count == 0) if (indexPaths.count == 0)
return; return;
LOG(@"insertNodes:%@ ofKind:%@", nodes, kind);
NSMutableArray *editingNodes = _editingNodes[kind]; NSMutableArray *editingNodes = _editingNodes[kind];
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes);
_editingNodes[kind] = editingNodes; _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 = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes);
[_mainSerialQueue performBlockOnMainThread:^{
_completedNodes[kind] = completedNodes;
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
}];
} }
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock - (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths
{ {
if (indexPaths.count == 0) { if (indexPaths.count == 0) {
return; return @[];
} }
LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@,", indexPaths, kind);
NSMutableArray *editingNodes = _editingNodes[kind]; NSMutableArray *editingNodes = _editingNodes[kind];
NSArray *deletedNodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths);
_editingNodes[kind] = editingNodes; _editingNodes[kind] = editingNodes;
return deletedNodes;
[_mainSerialQueue performBlockOnMainThread:^{
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths);
ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths);
if (completionBlock) {
completionBlock(nodes, indexPaths);
}
}];
} }
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock - (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet{
{
if (indexSet.count == 0) if (indexSet.count == 0)
return; return;
LOG(@"insertSections:%@ ofKind:%@", sections, kind);
if (_editingNodes[kind] == nil) { if (_editingNodes[kind] == nil) {
_editingNodes[kind] = [NSMutableArray array]; _editingNodes[kind] = [NSMutableArray array];
} }
[_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; [_editingNodes[kind] insertObjects:sections atIndexes:indexSet];
// Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads.
NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections);
[_mainSerialQueue performBlockOnMainThread:^{
[_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet];
if (completionBlock) {
completionBlock(sections, indexSet);
}
}];
} }
- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock - (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet
{ {
if (indexSet.count == 0) if (indexSet.count == 0)
return; return;
LOG(@"deleteSectionsOfKind:%@", kind);
[_editingNodes[kind] removeObjectsAtIndexes:indexSet]; [_editingNodes[kind] removeObjectsAtIndexes:indexSet];
}
- (void)moveSection:(NSInteger)section ofKind:(NSString *)kind toSection:(NSInteger)newSection
{
NSArray *movedSection = _editingNodes[kind][section];
[_editingNodes[kind] removeObjectAtIndex:section];
[_editingNodes[kind] insertObject:movedSection atIndex:newSection];
}
- (void)commitChangesToNodesOfKind:(NSString *)kind withCompletion:(void (^)())completionBlock
{
NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_editingNodes[kind]);
[_mainSerialQueue performBlockOnMainThread:^{ [_mainSerialQueue performBlockOnMainThread:^{
[_completedNodes[kind] removeObjectsAtIndexes:indexSet]; _completedNodes[kind] = completedNodes;
if (completionBlock) { if (completionBlock) {
completionBlock(indexSet); completionBlock();
} }
}]; }];
} }
#pragma mark - Internal Data Querying + Editing #pragma mark - Reload (External API)
/** - (void)reloadDataWithCompletion:(void (^)())completion
* Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes.
*
* @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy
* of the editing nodes. The delegate is invoked on the main thread.
*/
- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
[self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { [self _reloadDataSynchronously:NO completion:completion];
if (_delegateDidInsertNodes)
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
} }
/** - (void)reloadDataImmediately
* Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed.
*
* @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread.
* Once the backing stores are consistent, the delegate is invoked on the main thread.
*/
- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
[self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { [self _reloadDataSynchronously:YES completion:nil];
if (_delegateDidDeleteNodes)
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
} }
/** - (void)_reloadDataSynchronously:(BOOL)synchronously completion:(void (^)())completion
* Inserts sections, represented as arrays, into the backing store at the given indicies and notifies the delegate.
*
* @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted
* in the completed store on the main thread. The delegate is invoked on the main thread.
*/
- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) {
if (_delegateDidInsertSections)
[_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions];
}];
}
/**
* Removes sections at the given indicies from the backing store and notifies the delegate.
*
* @discussion Section array are first removed from the editing store, then the associated section in the completed
* store is removed on the main thread. The delegate is invoked on the main thread.
*/
- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) {
if (_delegateDidDeleteSections)
[_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
}];
}
#pragma mark - Initial Load & Full Reload (External API)
- (void)initialDataLoadingWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
[self accessDataSourceWithBlock:^{
NSMutableArray *indexPaths = [NSMutableArray array];
NSUInteger sectionNum = [_dataSource numberOfSectionsInDataController:self];
// insert sections
[self insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionNum)] withAnimationOptions:0];
for (NSUInteger i = 0; i < sectionNum; i++) {
NSIndexPath *indexPath = [[NSIndexPath alloc] initWithIndex:i];
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:i];
for (NSUInteger j = 0; j < rowNum; j++) {
[indexPaths addObject:[indexPath indexPathByAddingIndex:j]];
}
}
// insert elements
[self insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}];
}
- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion
{
[self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion];
}
- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil];
}
- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion
{ {
[self performEditCommandWithBlock:^{ [self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -417,39 +321,35 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[self accessDataSourceSynchronously:synchronously withBlock:^{ [self accessDataSourceSynchronously:synchronously withBlock:^{
NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self];
NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedNodeBlocks = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromEntireDataSourceWithMutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self _populateFromEntireDataSourceWithMutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths];
// 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];
void (^transactionBlock)() = ^{ void (^transactionBlock)() = ^{
LOG(@"Edit Transaction - reloadData"); LOG(@"Edit Transaction - reloadData");
// Remove everything that existed before the reload, now that we're ready to insert replacements
NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind]);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
[self willReloadData]; [self willReloadData];
// Insert each section // Insert sections
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) { for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]]; [sections addObject:[[NSMutableArray alloc] init]];
} }
_editingNodes[ASDataControllerRowNodeKind] = sections;
[self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions];
[self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (completion) { if (_delegateDidReloadData) {
dispatch_async(dispatch_get_main_queue(), completion); [_delegate dataControllerDidReloadData:self];
} }
if (completion) {
completion();
}
}];
}];
}; };
if (synchronously) { if (synchronously) {
@ -548,15 +448,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
if (_batchUpdateCounter == 0) { if (_batchUpdateCounter == 0) {
LOG(@"endUpdatesWithCompletion - beginning"); LOG(@"endUpdatesWithCompletion - beginning");
[_editingTransactionQueue addOperationWithBlock:^{ [_mainSerialQueue performBlockOnMainThread:^{
[_mainSerialQueue performBlockOnMainThread:^{ [_delegate dataControllerBeginUpdates:self];
// Deep copy _completedNodes to _externalCompletedNodes.
// Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate.
_externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]);
LOG(@"endUpdatesWithCompletion - begin updates call to delegate");
[_delegate dataControllerBeginUpdates:self];
}];
}]; }];
// Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates.
@ -567,15 +460,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
block(); block();
}]; }];
[_pendingEditCommandBlocks removeAllObjects]; [_pendingEditCommandBlocks removeAllObjects];
[_editingTransactionQueue addOperationWithBlock:^{ [_mainSerialQueue performBlockOnMainThread:^{
[_mainSerialQueue performBlockOnMainThread:^{ [_delegate dataController:self endUpdatesAnimated:animated completion:completion];
// Now that the transaction is done, _completedNodes can be accessed externally again.
_externalCompletedNodes = nil;
LOG(@"endUpdatesWithCompletion - calling delegate end");
[_delegate dataController:self endUpdatesAnimated:animated completion:completion];
}];
}]; }];
} }
} }
@ -607,9 +494,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished]; [_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{ [self accessDataSourceWithBlock:^{
NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedNodeBlocks = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths];
[self prepareForInsertSections:sections]; [self prepareForInsertSections:sections];
@ -622,8 +509,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[sectionArray addObject:[NSMutableArray array]]; [sectionArray addObject:[NSMutableArray array]];
} }
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; [self insertSections:sectionArray ofKind:ASDataControllerRowNodeKind atIndexSet:sections];
[self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
[self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidInsertSections)
[_delegate dataController:self didInsertSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
}];
}];
}]; }];
}]; }];
}]; }];
@ -641,10 +534,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// remove elements // remove elements
LOG(@"Edit Transaction - deleteSections: %@", sections); LOG(@"Edit Transaction - deleteSections: %@", sections);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
[self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:sections];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
[self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; if (_delegateDidDeleteSections)
[_delegate dataController:self didDeleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
}];
}]; }];
}]; }];
} }
@ -658,29 +553,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue waitUntilAllOperationsAreFinished]; [_editingTransactionQueue waitUntilAllOperationsAreFinished];
[self accessDataSourceWithBlock:^{ [self accessDataSourceWithBlock:^{
NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedNodeBlocks = [NSMutableArray array];
NSMutableArray *updatedIndexPaths = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array];
[self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths];
[self prepareForReloadSections:sections]; [self prepareForReloadSections:sections];
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
[self willReloadSections:sections]; [self willReloadSections:sections];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); // clear sections
[sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); _editingNodes[ASDataControllerRowNodeKind][idx] = [[NSMutableArray alloc] init];
}];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// reinsert the elements [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidReloadSections)
[_delegate dataController:self didReloadSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
}];
}];
}]; }];
}]; }];
}]; }];
} }
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
{ {
[self performEditCommandWithBlock:^{ [self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -690,24 +588,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
[self willMoveSection:section toSection:newSection]; [self willMoveSection:section toSection:newSection];
// remove elements
LOG(@"Edit Transaction - moveSection"); LOG(@"Edit Transaction - moveSection");
[self moveSection:section ofKind:ASDataControllerRowNodeKind toSection:newSection];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); if (_delegateDidMoveSection) {
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; [_delegate dataController:self didMoveSection:section toSection:newSection];
}
// update the section of indexpaths }];
NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection];
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in indexPaths) {
[updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]];
}
// Don't re-calculate size for moving
[self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
}]; }];
}]; }];
} }
@ -775,7 +663,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - insertRows: %@", indexPaths); LOG(@"Edit Transaction - insertRows: %@", indexPaths);
[self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; [self layoutAndInsertFromNodeBlocks:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidInsertNodes)
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}];
}]; }];
}]; }];
}]; }];
@ -795,7 +688,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - deleteRows: %@", indexPaths); LOG(@"Edit Transaction - deleteRows: %@", indexPaths);
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; NSArray *deletedNodes = [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:sortedIndexPaths];
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidDeleteNodes)
[_delegate dataController:self didDeleteNodes:deletedNodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}]; }];
}]; }];
} }
@ -810,20 +707,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source.
[self accessDataSourceWithBlock:^{ [self accessDataSourceWithBlock:^{
NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; NSMutableArray *nodeBlocks = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
// FIXME: This doesn't currently do anything // FIXME: This doesn't currently do anything
// FIXME: Shouldn't deletes be sorted in descending order? // FIXME: Shouldn't deletes be sorted in descending order?
[indexPaths sortedArrayUsingSelector:@selector(compare:)]; [indexPaths sortedArrayUsingSelector:@selector(compare:)];
for (NSIndexPath *indexPath in indexPaths) { for (NSIndexPath *indexPath in indexPaths) {
[nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; [nodeBlocks addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]];
} }
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - reloadRows: %@", indexPaths); LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths];
[self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; [self layoutAndInsertFromNodeBlocks:nodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidReloadNodes)
[_delegate dataController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
}];
}];
}]; }];
}]; }];
}]; }];
@ -869,7 +771,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
}]; }];
} }
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{ {
[self performEditCommandWithBlock:^{ [self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -879,26 +781,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
[_editingTransactionQueue addOperationWithBlock:^{ [_editingTransactionQueue addOperationWithBlock:^{
LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]);
NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:@[indexPath]];
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// Don't re-calculate size for moving // Don't re-calculate size for moving
NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:@[newIndexPath]];
[self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{
if (_delegateDidMoveNode) {
[_delegate dataController:self didMoveNodeAtIndexPath:indexPath toIndexPath:newIndexPath];
}
}];
}]; }];
}]; }];
} }
#pragma mark - Data Querying (Subclass API) #pragma mark - Data Querying (Subclass API)
- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind - (NSMutableDictionary *)editingNode{
{ return _editingNodes;
return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil;
}
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind
{
return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array];
} }
- (NSMutableArray *)completedNodesOfKind:(NSString *)kind - (NSMutableArray *)completedNodesOfKind:(NSString *)kind
@ -965,11 +864,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]);
} }
/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise.
- (NSArray *)completedNodes - (NSArray *)completedNodes
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind]; return _completedNodes[ASDataControllerRowNodeKind];
} }
#pragma mark - Dealloc #pragma mark - Dealloc

View File

@ -163,6 +163,30 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Called for nodes reload.
*
* @param rangeController Sender.
*
* @param nodes Inserted nodes.
*
* @param indexPaths Index path of reloaded nodes.
*
* @param animationOptions Animation options. See ASDataControllerAnimationOptions.
*/
- (void)rangeController:(ASRangeController *)rangeController didReloadNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Called for movement of node.
*
* @param rangeController Sender.
*
* @param fromIndexPath Index path of moved node before the movement.
*
* @param toIndexPath Index path of moved node after the movement.
*/
- (void)rangeController:(ASRangeController *)rangeController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath;
/** /**
* Called for section insertion. * Called for section insertion.
* *
@ -174,6 +198,17 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)rangeController:(ASRangeController *)rangeController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Called for section reload.
*
* @param rangeController Sender.
*
* @param indexSet Index set of reloaded sections.
*
* @param animationOptions Animation options. See ASDataControllerAnimationOptions.
*/
- (void)rangeController:(ASRangeController *)rangeController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/** /**
* Called for section deletion. * Called for section deletion.
* *
@ -185,6 +220,24 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions;
/**
* Called for movement of section.
*
* @param rangeController Sender.
*
* @param fromIndex Index of moved section before the movement.
*
* @param toIndex Index of moved section after the movement.
*/
- (void)rangeController:(ASRangeController *)rangeController didMoveSection:(NSInteger)fromIndex toSection:(NSInteger)toIndex;
/**
* Called for reload data.
*
* @param rangeController Sender.
*/
- (void)rangeControllerDidReloadData:(ASRangeController *)rangeController;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -361,15 +361,30 @@
}); });
} }
- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray<ASCellNode *> *)nodes atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions{
ASDisplayNodeAssert(nodes.count == indexPaths.count, @"Invalid index path");
ASPerformBlockOnMainThread(^{
_rangeIsValid = NO;
[_delegate rangeController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
});
}
- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections");
ASPerformBlockOnMainThread(^{ ASPerformBlockOnMainThread(^{
_rangeIsValid = NO; _rangeIsValid = NO;
[_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
}); });
} }
- (void)dataController:(ASDataController *)dataController didReloadSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
ASPerformBlockOnMainThread(^{
_rangeIsValid = NO;
[_delegate rangeController:self didReloadSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
});
}
- (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions - (void)dataController:(ASDataController *)dataController didDeleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{ {
ASPerformBlockOnMainThread(^{ ASPerformBlockOnMainThread(^{
@ -378,4 +393,11 @@
}); });
} }
@end - (void)dataControllerDidReloadData:(ASDataController *)dataController{
ASPerformBlockOnMainThread(^{
_rangeIsValid = NO;
[_delegate rangeControllerDidReloadData:self];
});
}
@end