From a1061301e08a269c416ca7844b661a444ebcf012 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 19 Feb 2016 21:31:39 -0800 Subject: [PATCH] [ASDataController] Revert the reloadData optimizations again - need to fix apps relying on prior behavior. The optimization seems correct now, but apps like Pinterest have some core code relying on edit operation order that is actually not permitted by UIKit (and this diff) but were tolerated previously. We will re-land this once we have time to adapt the code. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 +- AsyncDisplayKit/ASCollectionView.mm | 105 ++++- AsyncDisplayKit/ASTableView.mm | 83 +++- .../Details/ASChangeSetDataController.m | 4 +- .../Details/ASCollectionDataController.mm | 81 ++-- .../Details/ASDataController+Subclasses.h | 39 +- AsyncDisplayKit/Details/ASDataController.h | 43 +- AsyncDisplayKit/Details/ASDataController.mm | 376 +++++++----------- AsyncDisplayKit/Details/ASRangeController.h | 53 +++ AsyncDisplayKit/Details/ASRangeController.mm | 28 +- 10 files changed, 477 insertions(+), 339 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index e57d0a3ff2..94e9a5bbf8 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -647,7 +647,7 @@ 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = ""; }; 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; }; 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionDataController.mm; sourceTree = ""; }; 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewFlowLayoutInspector.h; sourceTree = ""; }; 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = ""; }; @@ -694,7 +694,7 @@ 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASTableViewTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 430E7C8D1B4C23F100697A4C /* ASIndexPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexPath.h; sourceTree = ""; }; 430E7C8E1B4C23F100697A4C /* ASIndexPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIndexPath.m; sourceTree = ""; }; - 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index b69e685ed7..07484f3622 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -249,7 +249,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _superIsPendingDataLoad = YES; [super reloadData]; }); - [_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion]; + [_dataController reloadDataWithCompletion:completion]; } - (void)reloadData @@ -261,7 +261,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { ASDisplayNodeAssertMainThread(); _superIsPendingDataLoad = YES; - [_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController reloadDataImmediately]; [super reloadData]; } @@ -446,7 +446,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController moveSection:section toSection:newSection]; } - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths @@ -470,7 +470,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } - (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 { 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 { 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 - (void)nodeDidRelayout:(ASCellNode *)node sizeChanged:(BOOL)sizeChanged diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 99009d8eb9..5ef50f7d88 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -303,10 +303,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataWithCompletion:(void (^)())completion { - ASPerformBlockOnMainThread(^{ - [super reloadData]; - }); - [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; + [_dataController reloadDataWithCompletion:completion]; } - (void)reloadData @@ -317,8 +314,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataImmediately { ASDisplayNodeAssertMainThread(); - [_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone]; - [super reloadData]; + [_dataController reloadDataImmediately]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -433,7 +429,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; + [_dataController moveSection:section toSection:newSection]; } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation @@ -457,7 +453,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } #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 { 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:%ld", (long)fromIndex); + + + 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 { 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 - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath { diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.m b/AsyncDisplayKit/Details/ASChangeSetDataController.m index 666ea86ce7..bdc0c4993c 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.m +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.m @@ -131,7 +131,7 @@ [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; } else { - [super moveSection:section toSection:newSection withAnimationOptions:animationOptions]; + [super moveSection:section toSection:newSection]; } } @@ -174,7 +174,7 @@ [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; } else { - [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions]; + [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; } } diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 2408a57e1f..979acd30e3 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -49,37 +49,27 @@ [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadData { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind]; - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - - NSArray *editingNodes = [self editingNodesOfKind:kind]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; - [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; - - // Insert each section + // Insert sections NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[NSMutableArray array]]; } - [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; - - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + self.editingNode[kind] = sections; + + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)prepareForInsertSections:(NSIndexSet *)sections @@ -91,9 +81,6 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _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++) { [sectionArray addObject:[NSMutableArray array]]; } - - [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + + [self insertSections:sectionArray ofKind:kind atIndexSet:sections]; + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)willDeleteSections:(NSIndexSet *)sections { for (NSString *kind in [self supplementaryKinds]) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); - - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; + [self deleteSectionsOfKind:kind atIndexSet:sections]; + [self commitChangesToNodesOfKind:kind withCompletion:nil]; } } @@ -132,40 +118,31 @@ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - - // Measure loaded nodes before leaving the main thread - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; } } - (void)willReloadSections:(NSIndexSet *)sections { [_pendingNodes enumerateKeysAndObjectsUsingBlock:^(NSString *kind, NSMutableArray *nodes, BOOL *stop) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); - [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; - // reinsert the elements - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + // clear sections + [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + self.editingNode[kind][idx] = [[NSMutableArray alloc] init]; + }]; + // reinsert the elements + [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:kind withCompletion:nil]; }]; - [_pendingNodes removeObjectForKey:kind]; - [_pendingIndexPaths removeObjectForKey:kind]; }]; + + [_pendingNodes removeAllObjects]; + [_pendingIndexPaths removeAllObjects]; } - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { for (NSString *kind in [self supplementaryKinds]) { - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); - [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]; + [self moveSection:section ofKind:kind toSection:newSection]; + [self commitChangesToNodesOfKind:kind withCompletion:nil]; } } diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 32d787910e..54f1fc259a 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -14,16 +14,7 @@ @interface ASDataController (Subclasses) #pragma mark - Internal editing & completed store querying - -/** - * 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; +@property (nonatomic, strong, readonly) NSMutableDictionary *editingNode; /** * 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:`. */ -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /* * 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 /** - * 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 diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 6648275d9b..1766b7dfc0 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -91,16 +91,41 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)dataController:(ASDataController *)dataController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +/** + Called for reload of elements. + */ +- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; + +/** + Called for movement of elements. + */ +- (void)dataController:(ASDataController *)dataController didMoveNodeAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; + /** Called for insertion of sections. */ -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *> *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)dataController:(ASDataController *)dataController didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** Called for deletion of sections. */ - (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 /** @@ -137,14 +162,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (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 */ - (void)beginUpdates; @@ -159,7 +176,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; - (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 *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; @@ -175,11 +192,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (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 */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 32d62adac2..40bf365c25 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -30,7 +30,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; static void *kASSizingQueueContext = &kASSizingQueueContext; @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 *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. @@ -42,9 +41,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; BOOL _asyncDataFetchingEnabled; BOOL _delegateDidInsertNodes; + BOOL _delegateDidReloadNodes; BOOL _delegateDidDeleteNodes; + BOOL _delegateDidMoveNode; BOOL _delegateDidInsertSections; BOOL _delegateDidDeleteSections; + BOOL _delegateDidReloadSections; + BOOL _delegateDidMoveSection; + BOOL _delegateDidReloadData; } @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. _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes: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:)]; + _delegateDidMoveSection = [_delegate respondsToSelector:@selector(dataController:didMoveSection:toSection:)]; + _delegateDidReloadData = [_delegate respondsToSelector:@selector(dataControllerDidReloadData:)]; } + (NSUInteger)parallelProcessorCount @@ -110,17 +119,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { - NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; - - // Processing in batches - for (NSUInteger i = 0; i < indexPaths.count; i += blockSize) { - 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]; - } + [self _layoutNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths]; + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + }]; } - (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { @@ -144,21 +150,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; 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 { if (!nodes.count) { - return; + if (completionBlock) { + completionBlock(nodes, indexPaths); + } + return; } NSUInteger nodeCount = nodes.count; @@ -238,178 +236,84 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #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) return; + LOG(@"insertNodes:%@ ofKind:%@", nodes, kind); NSMutableArray *editingNodes = _editingNodes[kind]; ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); _editingNodes[kind] = editingNodes; - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSMutableArray *completedNodes = (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) { - return; + return @[]; } - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); + LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@,", indexPaths, kind); NSMutableArray *editingNodes = _editingNodes[kind]; + NSArray *deletedNodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; - - [_mainSerialQueue performBlockOnMainThread:^{ - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; + return deletedNodes; } -- (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) return; + LOG(@"insertSections:%@ ofKind:%@", sections, kind); if (_editingNodes[kind] == nil) { _editingNodes[kind] = [NSMutableArray array]; } [_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) return; + + LOG(@"deleteSectionsOfKind:%@", kind); [_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:^{ - [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; + _completedNodes[kind] = completedNodes; if (completionBlock) { - completionBlock(indexSet); + completionBlock(); } }]; } -#pragma mark - Internal Data Querying + Editing +#pragma mark - Reload (External API) -/** - * 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 +- (void)reloadDataWithCompletion:(void (^)())completion { - [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - if (_delegateDidInsertNodes) - [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _reloadDataSynchronously:NO completion:completion]; } -/** - * 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 +- (void)reloadDataImmediately { - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - if (_delegateDidDeleteNodes) - [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _reloadDataSynchronously:YES completion:nil]; } -/** - * 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 +- (void)_reloadDataSynchronously:(BOOL)synchronously completion:(void (^)())completion { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -417,39 +321,35 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self accessDataSourceSynchronously:synchronously withBlock:^{ NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [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 [self prepareForReloadData]; void (^transactionBlock)() = ^{ 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]; - - // Insert each section + + // Insert sections NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[[NSMutableArray alloc] init]]; } - - [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; + _editingNodes[ASDataControllerRowNodeKind] = sections; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - - if (completion) { - dispatch_async(dispatch_get_main_queue(), completion); - } + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadData) { + [_delegate dataControllerDidReloadData:self]; + } + if (completion) { + completion(); + } + }]; + }]; }; if (synchronously) { @@ -550,11 +450,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ [_mainSerialQueue performBlockOnMainThread:^{ - // 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]; }]; }]; @@ -567,13 +462,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; block(); }]; [_pendingEditCommandBlocks removeAllObjects]; - + [_editingTransactionQueue addOperationWithBlock:^{ + // scheduling this block on _editingTransactionQueue is crucial. + // we must wait for all edit command blocks that happened before this one to schedule their main thread blocks first. [_mainSerialQueue performBlockOnMainThread:^{ - // 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 +500,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; [self prepareForInsertSections:sections]; @@ -622,8 +515,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [sectionArray addObject:[NSMutableArray array]]; } - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self insertSections:sectionArray ofKind:ASDataControllerRowNodeKind atIndexSet:sections]; + + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidInsertSections) + [_delegate dataController:self didInsertSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -641,10 +540,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // remove elements LOG(@"Edit Transaction - deleteSections: %@", sections); - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + + [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:sections]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidDeleteSections) + [_delegate dataController:self didDeleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }]; }]; }]; } @@ -658,29 +559,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodes = [NSMutableArray array]; + NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; [self prepareForReloadSections:sections]; [_editingTransactionQueue addOperationWithBlock:^{ [self willReloadSections:sections]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + // clear sections + [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { + _editingNodes[ASDataControllerRowNodeKind][idx] = [[NSMutableArray alloc] init]; + }]; - // reinsert the elements - [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [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:^{ ASDisplayNodeAssertMainThread(); @@ -690,24 +594,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ [self willMoveSection:section toSection:newSection]; - - // remove elements LOG(@"Edit Transaction - moveSection"); - - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - // update the section of indexpaths - NSIndexPath *sectionIndexPath = [[NSIndexPath alloc] initWithIndex:newSection]; - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSIndexPath *indexPath in indexPaths) { - [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; - } - - // Don't re-calculate size for moving - [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + [self moveSection:section ofKind:ASDataControllerRowNodeKind toSection:newSection]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidMoveSection) { + [_delegate dataController:self didMoveSection:section toSection:newSection]; + } + }]; }]; }]; } @@ -775,7 +669,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self layoutAndInsertFromNodeBlocks:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidInsertNodes) + [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -795,7 +694,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ 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 +713,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self accessDataSourceWithBlock:^{ - NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + NSMutableArray *nodeBlocks = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; // FIXME: This doesn't currently do anything // FIXME: Shouldn't deletes be sorted in descending order? [indexPaths sortedArrayUsingSelector:@selector(compare:)]; for (NSIndexPath *indexPath in indexPaths) { - [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + [nodeBlocks addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths]; + [self layoutAndInsertFromNodeBlocks:nodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidReloadNodes) + [_delegate dataController:self didReloadNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; + }]; }]; }]; }]; @@ -869,7 +777,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -879,26 +787,23 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]); - NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:@[indexPath]]; // Don't re-calculate size for moving - NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; - [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; + [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:@[newIndexPath]]; + [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ + if (_delegateDidMoveNode) { + [_delegate dataController:self didMoveNodeAtIndexPath:indexPath toIndexPath:newIndexPath]; + } + }]; }]; }]; } #pragma mark - Data Querying (Subclass API) -- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil; -} - -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array]; +- (NSMutableDictionary *)editingNode{ + return _editingNodes; } - (NSMutableArray *)completedNodesOfKind:(NSString *)kind @@ -965,11 +870,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; 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 { ASDisplayNodeAssertMainThread(); - return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind]; + return _completedNodes[ASDataControllerRowNodeKind]; } #pragma mark - Dealloc diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index d5288f40e2..c3e6f31551 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -163,6 +163,30 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)rangeController:(ASRangeController *)rangeController didDeleteNodes:(NSArray *)nodes atIndexPaths:(NSArray *)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 *)nodes atIndexPaths:(NSArray *)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. * @@ -174,6 +198,17 @@ NS_ASSUME_NONNULL_BEGIN */ - (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. * @@ -185,6 +220,24 @@ NS_ASSUME_NONNULL_BEGIN */ - (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 NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 1b8d7d8f89..c13ef1cdf6 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -361,15 +361,30 @@ }); } -- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions +- (void)dataController:(ASDataController *)dataController didReloadNodes:(NSArray *)nodes atIndexPaths:(NSArray *)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(^{ _rangeIsValid = NO; [_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 { ASPerformBlockOnMainThread(^{ @@ -378,4 +393,11 @@ }); } -@end \ No newline at end of file +- (void)dataControllerDidReloadData:(ASDataController *)dataController{ + ASPerformBlockOnMainThread(^{ + _rangeIsValid = NO; + [_delegate rangeControllerDidReloadData:self]; + }); +} + +@end