From b75d6627bb57ded3ae079071e1e10558400f4324 Mon Sep 17 00:00:00 2001 From: appleguy Date: Fri, 19 Feb 2016 23:26:29 -0800 Subject: [PATCH] Revert "Revert "Revert "[ASCollectionView / ASTableView] Optimize reloadData and reloadSection: methods.""" --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 +- AsyncDisplayKit/ASCollectionView.mm | 105 +---- AsyncDisplayKit/ASTableView.mm | 83 +--- .../Details/ASChangeSetDataController.m | 4 +- .../Details/ASCollectionDataController.mm | 79 ++-- .../Details/ASDataController+Subclasses.h | 39 +- AsyncDisplayKit/Details/ASDataController.h | 43 +- AsyncDisplayKit/Details/ASDataController.mm | 400 +++++++++++------- AsyncDisplayKit/Details/ASRangeController.h | 53 --- AsyncDisplayKit/Details/ASRangeController.mm | 28 +- 10 files changed, 352 insertions(+), 484 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index ac70348372..e57d0a3ff2 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 = ""; }; + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASCollectionDataController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 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 = ""; }; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 20e30b7c40..77fc3dd60d 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -252,7 +252,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _superIsPendingDataLoad = YES; [super reloadData]; }); - [_dataController reloadDataWithCompletion:completion]; + [_dataController reloadDataWithAnimationOptions:kASCollectionViewAnimationNone completion:completion]; } - (void)reloadData @@ -264,7 +264,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { ASDisplayNodeAssertMainThread(); _superIsPendingDataLoad = YES; - [_dataController reloadDataImmediately]; + [_dataController reloadDataImmediatelyWithAnimationOptions:kASCollectionViewAnimationNone]; [super reloadData]; } @@ -451,7 +451,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection]; + [_dataController moveSection:section toSection:newSection withAnimationOptions:kASCollectionViewAnimationNone]; } - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths @@ -475,7 +475,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; } - (NSString *)__reuseIdentifierForKind:(NSString *)kind @@ -974,46 +974,6 @@ 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(); @@ -1034,26 +994,6 @@ 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(); @@ -1074,43 +1014,6 @@ 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 a9f4d2aff9..923e690236 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -305,7 +305,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataWithCompletion:(void (^)())completion { - [_dataController reloadDataWithCompletion:completion]; + ASPerformBlockOnMainThread(^{ + [super reloadData]; + }); + [_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion]; } - (void)reloadData @@ -316,7 +319,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)reloadDataImmediately { ASDisplayNodeAssertMainThread(); - [_dataController reloadDataImmediately]; + [_dataController reloadDataImmediatelyWithAnimationOptions:UITableViewRowAnimationNone]; + [super reloadData]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -431,7 +435,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection { ASDisplayNodeAssertMainThread(); - [_dataController moveSection:section toSection:newSection]; + [_dataController moveSection:section toSection:newSection withAnimationOptions:UITableViewRowAnimationNone]; } - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation @@ -455,7 +459,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath { ASDisplayNodeAssertMainThread(); - [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:UITableViewRowAnimationNone]; } #pragma mark - @@ -851,36 +855,6 @@ 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(); @@ -897,36 +871,6 @@ 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 { ASDisplayNodeAssertMainThread(); @@ -942,17 +886,6 @@ 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 bdc0c4993c..666ea86ce7 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]; + [super moveSection:section toSection:newSection withAnimationOptions:animationOptions]; } } @@ -174,7 +174,7 @@ [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; } else { - [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; + [super moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:animationOptions]; } } diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 979acd30e3..2408a57e1f 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -49,27 +49,37 @@ [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) { - // Insert sections + // 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 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.editingNode[kind] = sections; - - [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + [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]; }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; - - [_pendingNodes removeAllObjects]; - [_pendingIndexPaths removeAllObjects]; } - (void)prepareForInsertSections:(NSIndexSet *)sections @@ -81,6 +91,9 @@ [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]; } } @@ -91,22 +104,23 @@ for (NSUInteger i = 0; i < sections.count; i++) { [sectionArray addObject:[NSMutableArray array]]; } - - [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]; + + [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]; }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; - - [_pendingNodes removeAllObjects]; - [_pendingIndexPaths removeAllObjects]; } - (void)willDeleteSections:(NSIndexSet *)sections { for (NSString *kind in [self supplementaryKinds]) { - [self deleteSectionsOfKind:kind atIndexSet:sections]; - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); + + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; + [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; } } @@ -118,31 +132,40 @@ [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) { - // clear sections - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - self.editingNode[kind][idx] = [[NSMutableArray alloc] init]; - }]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); + [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; // reinsert the elements - [self layoutAndInsertFromNodeBlocks:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; - - [_pendingNodes removeAllObjects]; - [_pendingIndexPaths removeAllObjects]; } - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { for (NSString *kind in [self supplementaryKinds]) { - [self moveSection:section ofKind:kind toSection:newSection]; - [self commitChangesToNodesOfKind:kind withCompletion:nil]; + 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]; } } diff --git a/AsyncDisplayKit/Details/ASDataController+Subclasses.h b/AsyncDisplayKit/Details/ASDataController+Subclasses.h index 54f1fc259a..32d787910e 100644 --- a/AsyncDisplayKit/Details/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Details/ASDataController+Subclasses.h @@ -14,7 +14,16 @@ @interface ASDataController (Subclasses) #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 @@ -26,7 +35,7 @@ /** * Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`. */ -- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; +- (void)batchLayoutNodes:(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. @@ -44,34 +53,24 @@ #pragma mark - Node & Section Insertion/Deletion API /** - * Inserts the given nodes of the specified kind into the backing store. + * Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes. */ -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /** - * Deletes the given nodes of the specified kind in the backing store. + * Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes. */ -- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths; +- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock; /** - * Inserts the given sections of the specified kind in the backing store. + * Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished. */ -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet; +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock; /** - * Deletes the given sections of the specified kind in the backing store. + * Deletes the given sections of the specified kind in the backing store, calling completion on the main thread when finished. */ -- (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; +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock; #pragma mark - Data Manipulation Hooks diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 1766b7dfc0..6648275d9b 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -91,41 +91,16 @@ 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 didInsertSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *> *)sections atIndexSet:(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 /** @@ -162,6 +137,14 @@ 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; @@ -176,7 +159,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection; +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; @@ -192,11 +175,11 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ - (void)relayoutAllNodes; -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; -- (void)reloadDataWithCompletion:(void (^)())completion; +- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^ _Nullable)())completion; -- (void)reloadDataImmediately; +- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions; /** @name Data Querying */ diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 7a7d28e446..32d62adac2 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -30,6 +30,7 @@ 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. @@ -41,14 +42,9 @@ 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; @@ -96,13 +92,8 @@ 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:)]; - _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:)]; + _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; - _delegateDidMoveSection = [_delegate respondsToSelector:@selector(dataController:didMoveSection:toSection:)]; - _delegateDidReloadData = [_delegate respondsToSelector:@selector(dataControllerDidReloadData:)]; } + (NSUInteger)parallelProcessorCount @@ -119,14 +110,17 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - Cell Layout -- (void)layoutAndInsertFromNodeBlocks:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock +- (void)batchLayoutNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))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); - } - }]; + 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]; + } } - (void)layoutLoadedNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths { @@ -150,13 +144,21 @@ 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) { - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - return; + return; } NSUInteger nodeCount = nodes.count; @@ -236,84 +238,178 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; #pragma mark - External Data Querying + Editing -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths +- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { 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); + } + }]; } -- (NSArray *)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths +- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock { if (indexPaths.count == 0) { - return @[]; + return; } - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@,", indexPaths, kind); + LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForMultidimensionalArray(_editingNodes[kind])); NSMutableArray *editingNodes = _editingNodes[kind]; - NSArray *deletedNodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _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{ +- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock +{ if (indexSet.count == 0) return; - LOG(@"insertSections:%@ ofKind:%@", sections, kind); if (_editingNodes[kind] == nil) { _editingNodes[kind] = [NSMutableArray array]; } [_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; -} - -- (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]); - + + // 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] = completedNodes; + [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; if (completionBlock) { - completionBlock(); + completionBlock(sections, indexSet); } }]; } -#pragma mark - Reload (External API) - -- (void)reloadDataWithCompletion:(void (^)())completion +- (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock { - [self _reloadDataSynchronously:NO completion:completion]; + if (indexSet.count == 0) + return; + [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; + [_mainSerialQueue performBlockOnMainThread:^{ + [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; + if (completionBlock) { + completionBlock(indexSet); + } + }]; } -- (void)reloadDataImmediately +#pragma mark - Internal Data Querying + Editing + +/** + * 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 _reloadDataSynchronously:YES completion:nil]; + [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + if (_delegateDidInsertNodes) + [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; } -- (void)_reloadDataSynchronously:(BOOL)synchronously completion:(void (^)())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 +{ + [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { + if (_delegateDidDeleteNodes) + [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; + }]; +} + +/** + * 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:^{ ASDisplayNodeAssertMainThread(); @@ -321,35 +417,39 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [self accessDataSourceSynchronously:synchronously withBlock:^{ NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self]; - NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; + NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromEntireDataSourceWithMutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; + [self _populateFromEntireDataSourceWithMutableNodes:updatedNodes 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 sections + + // Insert each section NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[[NSMutableArray alloc] init]]; } - _editingNodes[ASDataControllerRowNodeKind] = sections; + + [self _insertSections:sections atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withAnimationOptions:animationOptions]; - [self layoutAndInsertFromNodeBlocks:updatedNodeBlocks ofKind:ASDataControllerRowNodeKind atIndexPaths:updatedIndexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidReloadData) { - [_delegate dataControllerDidReloadData:self]; - } - if (completion) { - completion(); - } - }]; - }]; + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + + if (completion) { + dispatch_async(dispatch_get_main_queue(), completion); + } }; if (synchronously) { @@ -448,8 +548,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (_batchUpdateCounter == 0) { LOG(@"endUpdatesWithCompletion - beginning"); - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataControllerBeginUpdates:self]; + [_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]; + }]; }]; // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. @@ -460,9 +567,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; block(); }]; [_pendingEditCommandBlocks removeAllObjects]; - - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; + + [_editingTransactionQueue addOperationWithBlock:^{ + [_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]; + }]; }]; } } @@ -494,9 +607,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; + NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self prepareForInsertSections:sections]; @@ -509,14 +622,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [sectionArray addObject:[NSMutableArray array]]; } - [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]; - }]; - }]; + [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; }]; }]; }]; @@ -534,12 +641,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // remove elements LOG(@"Edit Transaction - deleteSections: %@", sections); - - [self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:sections]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidDeleteSections) - [_delegate dataController:self didDeleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); + + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; }]; }]; } @@ -553,32 +658,29 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue waitUntilAllOperationsAreFinished]; [self accessDataSourceWithBlock:^{ - NSMutableArray *updatedNodeBlocks = [NSMutableArray array]; + NSMutableArray *updatedNodes = [NSMutableArray array]; NSMutableArray *updatedIndexPaths = [NSMutableArray array]; - [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodeBlocks mutableIndexPaths:updatedIndexPaths]; + [self _populateFromDataSourceWithSectionIndexSet:sections mutableNodes:updatedNodes mutableIndexPaths:updatedIndexPaths]; [self prepareForReloadSections:sections]; [_editingTransactionQueue addOperationWithBlock:^{ [self willReloadSections:sections]; - // clear sections - [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - _editingNodes[ASDataControllerRowNodeKind][idx] = [[NSMutableArray alloc] init]; - }]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); + + LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForMultidimensionalArray(_editingNodes[ASDataControllerRowNodeKind])); + + [self _deleteNodesAtIndexPaths:indexPaths 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]; - }]; - }]; + // reinsert the elements + [self _batchLayoutNodes:updatedNodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; }]; }]; }]; } -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection +- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -588,14 +690,24 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ [self willMoveSection:section toSection:newSection]; + + // remove elements LOG(@"Edit Transaction - moveSection"); - [self moveSection:section ofKind:ASDataControllerRowNodeKind toSection:newSection]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidMoveSection) { - [_delegate dataController:self didMoveSection:section toSection:newSection]; - } - }]; + + 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]; }]; }]; } @@ -663,12 +775,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [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]; - }]; - }]; + [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; }]; }]; @@ -688,11 +795,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - deleteRows: %@", indexPaths); - NSArray *deletedNodes = [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:sortedIndexPaths]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidDeleteNodes) - [_delegate dataController:self didDeleteNodes:deletedNodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; + [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; }]; }]; } @@ -707,25 +810,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self accessDataSourceWithBlock:^{ - NSMutableArray *nodeBlocks = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + NSMutableArray *nodes = [[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) { - [nodeBlocks addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; + [nodes addObject:[_dataSource dataController:self nodeBlockAtIndexPath:indexPath]]; } [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - reloadRows: %@", indexPaths); - [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]; - }]; - }]; + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _batchLayoutNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; }]; }]; }]; @@ -771,7 +869,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; }]; } -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath +- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { [self performEditCommandWithBlock:^{ ASDisplayNodeAssertMainThread(); @@ -781,23 +879,26 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], [NSArray arrayWithObject:indexPath]); - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:@[indexPath]]; + NSArray *indexPaths = [NSArray arrayWithObject:indexPath]; + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; // Don't re-calculate size for moving - [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:@[newIndexPath]]; - [self commitChangesToNodesOfKind:ASDataControllerRowNodeKind withCompletion:^{ - if (_delegateDidMoveNode) { - [_delegate dataController:self didMoveNodeAtIndexPath:indexPath toIndexPath:newIndexPath]; - } - }]; + NSArray *newIndexPaths = [NSArray arrayWithObject:newIndexPath]; + [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; }]; }]; } #pragma mark - Data Querying (Subclass API) -- (NSMutableDictionary *)editingNode{ - return _editingNodes; +- (NSArray *)indexPathsForEditingNodesOfKind:(NSString *)kind +{ + return _editingNodes[kind] != nil ? ASIndexPathsForMultidimensionalArray(_editingNodes[kind]) : nil; +} + +- (NSMutableArray *)editingNodesOfKind:(NSString *)kind +{ + return _editingNodes[kind] != nil ? _editingNodes[kind] : [NSMutableArray array]; } - (NSMutableArray *)completedNodesOfKind:(NSString *)kind @@ -864,10 +965,11 @@ 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 _completedNodes[ASDataControllerRowNodeKind]; + return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes[ASDataControllerRowNodeKind]; } #pragma mark - Dealloc diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index c3e6f31551..d5288f40e2 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -163,30 +163,6 @@ 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. * @@ -198,17 +174,6 @@ 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. * @@ -220,24 +185,6 @@ 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 c13ef1cdf6..1b8d7d8f89 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -361,30 +361,15 @@ }); } -- (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 +- (void)dataController:(ASDataController *)dataController didInsertSections:(NSArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { + ASDisplayNodeAssert(sections.count == indexSet.count, @"Invalid sections"); ASPerformBlockOnMainThread(^{ _rangeIsValid = NO; [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; }); } -- (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(^{ @@ -393,11 +378,4 @@ }); } -- (void)dataControllerDidReloadData:(ASDataController *)dataController{ - ASPerformBlockOnMainThread(^{ - _rangeIsValid = NO; - [_delegate rangeControllerDidReloadData:self]; - }); -} - -@end +@end \ No newline at end of file