Merge pull request #1812 from facebook/AHUpdateIntegrity

[ASDataController] Improve Update Handling, Esp. Reloading Sections
This commit is contained in:
Adlai Holler 2016-06-27 18:11:11 -07:00 committed by GitHub
commit d82e1ce95b
15 changed files with 284 additions and 287 deletions

View File

@ -546,6 +546,8 @@
CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */; };
CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */; };
CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */; };
CC4981BC1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; };
CC4981BD1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */; };
CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; };
CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; };
CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; };
@ -937,6 +939,8 @@
CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakSetTests.m; sourceTree = "<group>"; };
CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBridgedPropertiesTests.mm; sourceTree = "<group>"; };
CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewThrashTests.m; sourceTree = "<group>"; };
CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexSet+ASHelpers.h"; sourceTree = "<group>"; };
CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexSet+ASHelpers.m"; sourceTree = "<group>"; };
CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = "<group>"; };
CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = "<group>"; };
CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = "<group>"; };
@ -1229,6 +1233,8 @@
058D09E1195D050800B7D73C /* Details */ = {
isa = PBXGroup;
children = (
CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */,
CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */,
9CFFC6BD1CCAC52B006A6476 /* ASEnvironment.mm */,
058D09E2195D050800B7D73C /* _ASDisplayLayer.h */,
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
@ -1634,6 +1640,7 @@
ACF6ED2D1B17843500DA7C62 /* ASRatioLayoutSpec.h in Headers */,
AC47D9451B3BB41900AAEE9D /* ASRelativeSize.h in Headers */,
291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */,
CC4981BC1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h in Headers */,
D785F6621A74327E00291744 /* ASScrollNode.h in Headers */,
058D0A7F195D05F900B7D73C /* ASSentinel.h in Headers */,
9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */,
@ -2101,6 +2108,7 @@
ACF6ED4C1B17847A00DA7C62 /* ASInternalHelpers.mm in Sources */,
68FC85DF1CE29AB700EDD713 /* ASNavigationController.m in Sources */,
ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */,
CC4981BD1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m in Sources */,
DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */,
92074A631CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */,
251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */,

View File

@ -498,18 +498,21 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)insertSections:(NSIndexSet *)sections
{
ASDisplayNodeAssertMainThread();
if (sections.count == 0) { return; }
[_dataController insertSections:sections withAnimationOptions:kASCollectionViewAnimationNone];
}
- (void)deleteSections:(NSIndexSet *)sections
{
ASDisplayNodeAssertMainThread();
if (sections.count == 0) { return; }
[_dataController deleteSections:sections withAnimationOptions:kASCollectionViewAnimationNone];
}
- (void)reloadSections:(NSIndexSet *)sections
{
ASDisplayNodeAssertMainThread();
if (sections.count == 0) { return; }
[_dataController reloadSections:sections withAnimationOptions:kASCollectionViewAnimationNone];
}
@ -522,18 +525,21 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
{
ASDisplayNodeAssertMainThread();
if (indexPaths.count == 0) { return; }
[_dataController insertRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone];
}
- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths
{
ASDisplayNodeAssertMainThread();
if (indexPaths.count == 0) { return; }
[_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone];
}
- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths
{
ASDisplayNodeAssertMainThread();
if (indexPaths.count == 0) { return; }
[_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone];
}

View File

@ -147,6 +147,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
// Always set, whether ASCollectionView is created directly or via ASCollectionNode.
@property (nonatomic, weak) ASTableNode *tableNode;
@property (nonatomic) BOOL test_enableSuperUpdateCallLogging;
@end
@implementation ASTableView
@ -459,18 +460,21 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
ASDisplayNodeAssertMainThread();
if (sections.count == 0) { return; }
[_dataController insertSections:sections withAnimationOptions:animation];
}
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
ASDisplayNodeAssertMainThread();
if (sections.count == 0) { return; }
[_dataController deleteSections:sections withAnimationOptions:animation];
}
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
{
ASDisplayNodeAssertMainThread();
if (sections.count == 0) { return; }
[_dataController reloadSections:sections withAnimationOptions:animation];
}
@ -483,18 +487,21 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
ASDisplayNodeAssertMainThread();
if (indexPaths.count == 0) { return; }
[_dataController insertRowsAtIndexPaths:indexPaths withAnimationOptions:animation];
}
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
ASDisplayNodeAssertMainThread();
if (indexPaths.count == 0) { return; }
[_dataController deleteRowsAtIndexPaths:indexPaths withAnimationOptions:animation];
}
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
ASDisplayNodeAssertMainThread();
if (indexPaths.count == 0) { return; }
[_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animation];
}
@ -978,6 +985,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
ASPerformBlockWithoutAnimation(preventAnimation, ^{
if (self.test_enableSuperUpdateCallLogging) {
NSLog(@"-[super insertRowsAtIndexPaths]: %@", indexPaths);
}
[super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count];
});
@ -998,6 +1008,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
ASPerformBlockWithoutAnimation(preventAnimation, ^{
if (self.test_enableSuperUpdateCallLogging) {
NSLog(@"-[super deleteRowsAtIndexPaths]: %@", indexPaths);
}
[super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexPaths.count];
});
@ -1019,6 +1032,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
ASPerformBlockWithoutAnimation(preventAnimation, ^{
if (self.test_enableSuperUpdateCallLogging) {
NSLog(@"-[super insertSections]: %@", indexSet);
}
[super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count];
});
@ -1035,6 +1051,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
ASPerformBlockWithoutAnimation(preventAnimation, ^{
if (self.test_enableSuperUpdateCallLogging) {
NSLog(@"-[super deleteSections]: %@", indexSet);
}
[super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions];
[self _scheduleCheckForBatchFetchingForNumberOfChanges:indexSet.count];
});

View File

@ -34,4 +34,7 @@
*/
- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass ownedByNode:(BOOL)ownedByNode;
/// Set YES and we'll log every time we call [super insertRows…] etc
@property (nonatomic) BOOL test_enableSuperUpdateCallLogging;
@end

View File

@ -14,6 +14,7 @@
#import "ASInternalHelpers.h"
#import "_ASHierarchyChangeSet.h"
#import "ASAssert.h"
#import "NSIndexSet+ASHelpers.h"
#import "ASDataController+Subclasses.h"
@ -47,6 +48,9 @@
[super beginUpdates];
NSAssert([_changeSet itemChangesOfType:_ASHierarchyChangeTypeReload].count == 0, @"Expected reload item changes to have been converted into insert/deletes.");
NSAssert([_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload].count == 0, @"Expected reload section changes to have been converted into insert/deletes.");
for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) {
[super deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
}
@ -54,17 +58,7 @@
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) {
[super deleteSections:change.indexSet withAnimationOptions:change.animationOptions];
}
// TODO: Shouldn't reloads be processed before deletes, since deletes affect
// the index space and reloads don't?
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) {
[super reloadSections:change.indexSet withAnimationOptions:change.animationOptions];
}
for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) {
[super reloadRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
}
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) {
[super insertSections:change.indexSet withAnimationOptions:change.animationOptions];
}
@ -115,7 +109,10 @@
if ([self batchUpdating]) {
[_changeSet reloadSections:sections animationOptions:animationOptions];
} else {
[super reloadSections:sections withAnimationOptions:animationOptions];
[self beginUpdates];
[super deleteSections:sections withAnimationOptions:animationOptions];
[super insertSections:sections withAnimationOptions:animationOptions];
[self endUpdates];
}
}
@ -158,7 +155,10 @@
if ([self batchUpdating]) {
[_changeSet reloadItems:indexPaths animationOptions:animationOptions];
} else {
[super reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self beginUpdates];
[super deleteRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[super insertRowsAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self endUpdates];
}
}

View File

@ -115,30 +115,6 @@
}
}
- (void)prepareForReloadSections:(NSIndexSet *)sections
{
for (NSString *kind in [self supplementaryKinds]) {
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts];
_pendingContexts[kind] = contexts;
}
}
- (void)willReloadSections:(NSIndexSet *)sections
{
NSArray *keys = _pendingContexts.allKeys;
for (NSString *kind in keys) {
NSMutableArray<ASIndexedNodeContext *> *contexts = _pendingContexts[kind];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections);
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
// reinsert the elements
[self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
[_pendingContexts removeObjectForKey:kind];
}
}
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
{
for (NSString *kind in [self supplementaryKinds]) {
@ -187,30 +163,6 @@
}
}
- (void)prepareForReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
for (NSString *kind in [self supplementaryKinds]) {
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
_pendingContexts[kind] = contexts;
}
}
- (void)willReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
NSArray *keys = _pendingContexts.allKeys;
for (NSString *kind in keys) {
NSMutableArray<ASIndexedNodeContext *> *contexts = _pendingContexts[kind];
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
// reinsert the elements
[self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
[_pendingContexts removeObjectForKey:kind];
}
}
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
{
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];

View File

@ -128,28 +128,6 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCellNode *> *nodes, NS
*/
- (void)willDeleteSections:(NSIndexSet *)sections;
/**
* Notifies the subclass to perform any work needed before the given sections will be reloaded.
*
* @discussion This method will be performed before the data controller enters its editing queue, usually on the main
* thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param sections Indices of sections to be reloaded
*/
- (void)prepareForReloadSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will reload the sections in the given index set
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param sections Indices of sections to be reloaded
*/
- (void)willReloadSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will move a section to a new position
*
@ -206,26 +184,4 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCellNode *> *nodes, NS
*/
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
/**
* Notifies the subclass to perform any work needed before the given rows will be reloaded.
*
* @discussion This method will be performed before the data controller enters its editing queue, usually on the main
* thread. The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param indexPaths Index paths for the rows to be reloaded.
*/
- (void)prepareForReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
/**
* Notifies the subclass that the data controller will reload the rows at the given index paths.
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param indexPaths Index paths for the rows to be reloaded.
*/
- (void)willReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
@end

View File

@ -65,6 +65,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
if (!(self = [super init])) {
return nil;
}
ASDisplayNodeAssert(![self isMemberOfClass:[ASDataController class]], @"ASDataController is an abstract class and should not be instantiated. Instantiate a subclass instead.");
_completedNodes = [NSMutableDictionary dictionary];
_editingNodes = [NSMutableDictionary dictionary];
@ -661,29 +662,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - reloadSections: %@", sections);
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
NSArray<ASIndexedNodeContext *> *contexts= [self _populateFromDataSourceWithSectionIndexSet:sections];
[self prepareForReloadSections:sections];
[_editingTransactionQueue addOperationWithBlock:^{
[self willReloadSections:sections];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForTwoDimensionalArray(_editingNodes[ASDataControllerRowNodeKind]));
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// reinsert the elements
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
}];
}];
ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController the reload will be broken into delete & insert.", NSStringFromSelector(_cmd));
}
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
@ -746,16 +725,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)prepareForReloadSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willReloadSections:(NSIndexSet *)sections
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
@ -781,16 +750,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)prepareForReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
- (void)willReloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
// Optional template hook for subclasses (See ASDataController+Subclasses.h)
}
#pragma mark - Row Editing (External API)
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
@ -853,40 +812,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - reloadRows: %@", indexPaths);
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
// Sort indexPath to avoid messing up the index when deleting
// FIXME: Shouldn't deletes be sorted in descending order?
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
for (NSIndexPath *indexPath in sortedIndexPaths) {
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
indexPath:indexPath
constrainedSize:constrainedSize
environmentTraitCollection:environmentTraitCollection]];
}
[self prepareForReloadRowsAtIndexPaths:indexPaths];
[_editingTransactionQueue addOperationWithBlock:^{
[self willReloadRowsAtIndexPaths:indexPaths];
LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
}];
}];
ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController and the reload will be broken into delete & insert.", NSStringFromSelector(_cmd));
}
- (void)relayoutAllNodes

View File

@ -92,12 +92,12 @@
currPath.row++;
// Once we reach the end of the section, advance to the next one. Keep advancing if the next section is zero-sized.
while (currPath.row >= [(NSArray *)completedNodes[currPath.section] count] && currPath.section < completedNodes.count - 1) {
while (currPath.row >= [(NSArray *)completedNodes[currPath.section] count] && currPath.section < endPath.section) {
currPath.row = 0;
currPath.section++;
ASDisplayNodeAssert(currPath.section <= endPath.section, @"currPath should never reach a further section than endPath");
}
}
ASDisplayNodeAssert(currPath.section <= endPath.section, @"currPath should never reach a further section than endPath");
[indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:endPath]];

View File

@ -0,0 +1,25 @@
//
// NSIndexSet+ASHelpers.h
// AsyncDisplayKit
//
// Created by Adlai Holler on 6/23/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSIndexSet (ASHelpers)
- (NSIndexSet *)as_indexesByMapping:(NSUInteger (^)(NSUInteger idx))block;
- (NSIndexSet *)as_intersectionWithIndexes:(NSIndexSet *)indexes;
/// Returns all the item indexes from the given index paths that are in the given section.
+ (NSIndexSet *)as_indexSetFromIndexPaths:(NSArray<NSIndexPath *> *)indexPaths inSection:(NSUInteger)section;
/// If you've got an old index, and you insert items using this index set, this returns the change to get to the new index.
- (NSUInteger)as_indexChangeByInsertingItemsBelowIndex:(NSUInteger)index;
- (NSString *)as_smallDescription;
@end

View File

@ -0,0 +1,76 @@
//
// NSIndexSet+ASHelpers.m
// AsyncDisplayKit
//
// Created by Adlai Holler on 6/23/16.
// Copyright © 2016 Facebook. All rights reserved.
//
@import UIKit;
#import "NSIndexSet+ASHelpers.h"
@implementation NSIndexSet (ASHelpers)
- (NSIndexSet *)as_indexesByMapping:(NSUInteger (^)(NSUInteger))block
{
NSMutableIndexSet *result = [NSMutableIndexSet indexSet];
[self enumerateIndexesUsingBlock:^(NSUInteger idx, __unused BOOL * _Nonnull stop) {
NSUInteger newIndex = block(idx);
if (newIndex != NSNotFound) {
[result addIndex:newIndex];
}
}];
return result;
}
- (NSIndexSet *)as_intersectionWithIndexes:(NSIndexSet *)indexes
{
NSMutableIndexSet *result = [NSMutableIndexSet indexSet];
[self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
[indexes enumerateRangesInRange:range options:kNilOptions usingBlock:^(NSRange range, BOOL * _Nonnull stop) {
[result addIndexesInRange:range];
}];
}];
return result;
}
+ (NSIndexSet *)as_indexSetFromIndexPaths:(NSArray<NSIndexPath *> *)indexPaths inSection:(NSUInteger)section
{
NSMutableIndexSet *result = [NSMutableIndexSet indexSet];
for (NSIndexPath *indexPath in indexPaths) {
if (indexPath.section == section) {
[result addIndex:indexPath.item];
}
}
return result;
}
- (NSUInteger)as_indexChangeByInsertingItemsBelowIndex:(NSUInteger)index
{
__block NSUInteger newIndex = index;
[self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
if (idx <= newIndex) {
newIndex += 1;
} else {
*stop = YES;
}
}];
return newIndex - index;
}
- (NSString *)as_smallDescription
{
NSMutableString *result = [NSMutableString stringWithString:@"{ "];
[self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
if (range.length == 1) {
[result appendFormat:@"%lu ", (unsigned long)range.location];
} else {
[result appendFormat:@"%lu-%lu ", (unsigned long)range.location, (unsigned long)NSMaxRange(range)];
}
}];
[result appendString:@"}"];
return result;
}
@end

View File

@ -13,6 +13,8 @@
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
NS_ASSUME_NONNULL_BEGIN
typedef NSUInteger ASDataControllerAnimationOptions;
typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) {
@ -21,6 +23,8 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) {
_ASHierarchyChangeTypeInsert
};
NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType);
@interface _ASHierarchySectionChange : NSObject
// FIXME: Generalize this to `changeMetadata` dict?
@ -34,11 +38,11 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) {
@property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions;
/// Index paths are sorted descending for changeType .Delete, ascending otherwise
@property (nonatomic, strong, readonly) NSArray *indexPaths;
@property (nonatomic, strong, readonly) NSArray<NSIndexPath *> *indexPaths;
@property (nonatomic, readonly) _ASHierarchyChangeType changeType;
+ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray *)changes ofType:(_ASHierarchyChangeType)changeType;
+ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofType:(_ASHierarchyChangeType)changeType;
@end
@interface _ASHierarchyChangeSet : NSObject
@ -47,8 +51,6 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) {
@property (nonatomic, strong, readonly) NSIndexSet *deletedSections;
/// @precondition The change set must be completed.
@property (nonatomic, strong, readonly) NSIndexSet *insertedSections;
/// @precondition The change set must be completed.
@property (nonatomic, strong, readonly) NSIndexSet *reloadedSections;
/**
Get the section index after the update for the given section before the update.
@ -56,11 +58,12 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) {
@precondition The change set must be completed.
@returns The new section index, or NSNotFound if the given section was deleted.
*/
- (NSInteger)newSectionForOldSection:(NSInteger)oldSection;
- (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection;
@property (nonatomic, readonly) BOOL completed;
/// Call this once the change set has been constructed to prevent future modifications to the changeset. Calling this more than once is a programmer error.
/// NOTE: Calling this method will cause the changeset to convert all reloads into delete/insert pairs.
- (void)markCompleted;
/**
@ -77,13 +80,18 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) {
- Inserted sections, ascending order
- Inserted items, ascending order
*/
- (NSArray /*<_ASHierarchySectionChange *>*/ *)sectionChangesOfType:(_ASHierarchyChangeType)changeType;
- (NSArray /*<_ASHierarchyItemChange *>*/ *)itemChangesOfType:(_ASHierarchyChangeType)changeType;
- (nullable NSArray <_ASHierarchySectionChange *> *)sectionChangesOfType:(_ASHierarchyChangeType)changeType;
- (nullable NSArray <_ASHierarchyItemChange *> *)itemChangesOfType:(_ASHierarchyChangeType)changeType;
/// Returns all item indexes affected by changes of the given type in the given section.
- (NSIndexSet *)indexesForItemChangesOfType:(_ASHierarchyChangeType)changeType inSection:(NSUInteger)section;
- (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options;
- (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options;
- (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options;
- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
- (void)insertItems:(NSArray<NSIndexPath *> *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
- (void)deleteItems:(NSArray<NSIndexPath *> *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
- (void)reloadItems:(NSArray<NSIndexPath *> *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options;
@end
NS_ASSUME_NONNULL_END

View File

@ -12,6 +12,22 @@
#import "_ASHierarchyChangeSet.h"
#import "ASInternalHelpers.h"
#import "NSIndexSet+ASHelpers.h"
#import "ASAssert.h"
NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType)
{
switch (changeType) {
case _ASHierarchyChangeTypeInsert:
return @"Insert";
case _ASHierarchyChangeTypeDelete:
return @"Delete";
case _ASHierarchyChangeTypeReload:
return @"Reload";
default:
return @"(invalid)";
}
}
@interface _ASHierarchySectionChange ()
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions;
@ -23,7 +39,7 @@
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes;
/// Returns all the indexes from all the `indexSet`s of the given `_ASHierarchySectionChange` objects.
+ (NSMutableIndexSet *)allIndexesInChanges:(NSArray *)changes;
+ (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray *)changes;
@end
@interface _ASHierarchyItemChange ()
@ -38,12 +54,12 @@
@interface _ASHierarchyChangeSet ()
@property (nonatomic, strong, readonly) NSMutableArray *insertItemChanges;
@property (nonatomic, strong, readonly) NSMutableArray *deleteItemChanges;
@property (nonatomic, strong, readonly) NSMutableArray *reloadItemChanges;
@property (nonatomic, strong, readonly) NSMutableArray *insertSectionChanges;
@property (nonatomic, strong, readonly) NSMutableArray *deleteSectionChanges;
@property (nonatomic, strong, readonly) NSMutableArray *reloadSectionChanges;
@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *insertItemChanges;
@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *deleteItemChanges;
@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *reloadItemChanges;
@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *insertSectionChanges;
@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *deleteSectionChanges;
@property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *reloadSectionChanges;
@end
@ -103,21 +119,27 @@
}
}
- (NSInteger)newSectionForOldSection:(NSInteger)oldSection
- (NSIndexSet *)indexesForItemChangesOfType:(_ASHierarchyChangeType)changeType inSection:(NSUInteger)section
{
[self _ensureCompleted];
NSMutableIndexSet *result = [NSMutableIndexSet indexSet];
for (_ASHierarchyItemChange *change in [self itemChangesOfType:changeType]) {
[result addIndexes:[NSIndexSet as_indexSetFromIndexPaths:change.indexPaths inSection:section]];
}
return result;
}
- (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection
{
ASDisplayNodeAssertNotNil(_deletedSections, @"Cannot call %@ before `markCompleted` returns.", NSStringFromSelector(_cmd));
ASDisplayNodeAssertNotNil(_insertedSections, @"Cannot call %@ before `markCompleted` returns.", NSStringFromSelector(_cmd));
[self _ensureCompleted];
if ([_deletedSections containsIndex:oldSection]) {
return NSNotFound;
}
__block NSInteger newIndex = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)];
[_insertedSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
if (idx <= newIndex) {
newIndex += 1;
} else {
*stop = YES;
}
}];
NSUInteger newIndex = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)];
newIndex += [_insertedSections as_indexChangeByInsertingItemsBelowIndex:newIndex];
return newIndex;
}
@ -180,42 +202,42 @@
- (void)_sortAndCoalesceChangeArrays
{
@autoreleasepool {
// Split reloaded sections into [delete(oldIndex), insert(newIndex)]
// Give these their "pre-reloads" values. Once we add in the reloads we'll re-process them.
_deletedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_deleteSectionChanges];
_insertedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_insertSectionChanges];
for (_ASHierarchySectionChange *change in _reloadSectionChanges) {
NSIndexSet *newSections = [change.indexSet as_indexesByMapping:^(NSUInteger idx) {
NSUInteger newSec = [self newSectionForOldSection:idx];
NSAssert(newSec != NSNotFound, @"Request to reload deleted section %lu", (unsigned long)idx);
return newSec;
}];
_ASHierarchySectionChange *deleteChange = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexSet:change.indexSet animationOptions:change.animationOptions];
[_deleteSectionChanges addObject:deleteChange];
_ASHierarchySectionChange *insertChange = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexSet:newSections animationOptions:change.animationOptions];
[_insertSectionChanges addObject:insertChange];
}
_reloadSectionChanges = nil;
[_ASHierarchySectionChange sortAndCoalesceChanges:_deleteSectionChanges];
[_ASHierarchySectionChange sortAndCoalesceChanges:_insertSectionChanges];
[_ASHierarchySectionChange sortAndCoalesceChanges:_reloadSectionChanges];
_deletedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_deleteSectionChanges];
_insertedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_insertSectionChanges];
_deletedSections = [[_ASHierarchySectionChange allIndexesInChanges:_deleteSectionChanges] copy];
_insertedSections = [[_ASHierarchySectionChange allIndexesInChanges:_insertSectionChanges] copy];
_reloadedSections = [[_ASHierarchySectionChange allIndexesInChanges:_reloadSectionChanges] copy];
// These are invalid old section indexes.
NSMutableIndexSet *deletedOrReloaded = [_deletedSections mutableCopy];
[deletedOrReloaded addIndexes:_reloadedSections];
// These are invalid new section indexes.
NSMutableIndexSet *insertedOrReloaded = [_insertedSections mutableCopy];
// Get the new section that each reloaded section index corresponds to.
// Coalesce reload sections' indexes into deletes and inserts
[_reloadedSections enumerateIndexesUsingBlock:^(NSUInteger oldIndex, __unused BOOL * stop) {
NSUInteger newIndex = [self newSectionForOldSection:oldIndex];
if (newIndex != NSNotFound) {
[insertedOrReloaded addIndex:newIndex];
}
[deletedOrReloaded addIndex:oldIndex];
}];
_deletedSections = deletedOrReloaded;
_insertedSections = insertedOrReloaded;
_reloadedSections = nil;
// reload items changes need to be adjusted so that we access the correct indexPaths in the datasource
// Split reloaded items into [delete(oldIndexPath), insert(newIndexPath)]
NSDictionary *insertedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_insertItemChanges ofType:_ASHierarchyChangeTypeInsert];
NSDictionary *deletedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_deleteItemChanges ofType:_ASHierarchyChangeTypeDelete];
for (_ASHierarchyItemChange *change in _reloadItemChanges) {
NSAssert(change.changeType == _ASHierarchyChangeTypeReload, @"It must be a reload change to be in here");
NSMutableArray *newIndexPaths = [NSMutableArray array];
NSMutableArray *newIndexPaths = [NSMutableArray arrayWithCapacity:change.indexPaths.count];
// Every indexPaths in the change need to update its section and/or row
// depending on all the deletions and insertions
@ -223,39 +245,21 @@
// - delete/reload indexPaths that are passed in should all be their current indexPaths
// - insert indexPaths that are passed in should all be their future indexPaths after deletions
for (NSIndexPath *indexPath in change.indexPaths) {
__block NSUInteger section = indexPath.section;
__block NSUInteger row = indexPath.row;
// Update section number based on section insertions/deletions that are above the current section
section -= [_deletedSections countOfIndexesInRange:NSMakeRange(0, section)];
[_insertedSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
if (idx <= section) {
section += 1;
} else {
*stop = YES;
}
}];
NSUInteger section = [self newSectionForOldSection:indexPath.section];
NSUInteger item = indexPath.item;
// Update row number based on deletions that are above the current row in the current section
NSIndexSet *indicesDeletedInSection = deletedIndexPathsMap[@(indexPath.section)];
row -= [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, row)];
item -= [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, item)];
// Update row number based on insertions that are above the current row in the future section
NSIndexSet *indicesInsertedInSection = insertedIndexPathsMap[@(section)];
[indicesInsertedInSection enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
if (idx <= row) {
row += 1;
} else {
*stop = YES;
}
}];
item += [indicesInsertedInSection as_indexChangeByInsertingItemsBelowIndex:item];
//TODO: reuse the old indexPath object if section and row aren't changed
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:row inSection:section];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
[newIndexPaths addObject:newIndexPath];
}
// All reload changes are coalesced into deletes and inserts
// All reload changes are translated into deletes and inserts
// We delete the items that needs reload together with other deleted items, at their original index
_ASHierarchyItemChange *deleteItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:change.indexPaths animationOptions:change.animationOptions presorted:NO];
[_deleteItemChanges addObject:deleteItemChangeFromReloadChange];
@ -263,16 +267,20 @@
_ASHierarchyItemChange *insertItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:newIndexPaths animationOptions:change.animationOptions presorted:NO];
[_insertItemChanges addObject:insertItemChangeFromReloadChange];
}
[_reloadItemChanges removeAllObjects];
_reloadItemChanges = nil;
// Ignore item deletes in reloaded/deleted sections.
[_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:deletedOrReloaded];
[_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:_deletedSections];
// Ignore item inserts in reloaded(new)/inserted sections.
[_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:insertedOrReloaded];
[_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:_insertedSections];
}
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@ %p: deletedSections=%@, insertedSections=%@, deletedItems=%@, insertedItems=%@>", NSStringFromClass(self.class), self, _deletedSections, _insertedSections, _deleteItemChanges, _insertItemChanges];
}
@end
@ -283,6 +291,7 @@
{
self = [super init];
if (self) {
ASDisplayNodeAssert(indexSet.count > 0, @"Request to create _ASHierarchySectionChange with no sections!");
_changeType = changeType;
_indexSet = indexSet;
_animationOptions = animationOptions;
@ -346,7 +355,7 @@
[changes setArray:result];
}
+ (NSMutableIndexSet *)allIndexesInChanges:(NSArray *)changes
+ (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes
{
NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
for (_ASHierarchySectionChange *change in changes) {
@ -355,6 +364,11 @@
return indexes;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: anim=%lu, type=%@, indexes=%@>", NSStringFromClass(self.class), (unsigned long)_animationOptions, NSStringFromASHierarchyChangeType(_changeType), [self.indexSet as_smallDescription]];
}
@end
@implementation _ASHierarchyItemChange
@ -363,6 +377,7 @@
{
self = [super init];
if (self) {
ASDisplayNodeAssert(indexPaths.count > 0, @"Request to create _ASHierarchyItemChange with no items!");
_changeType = changeType;
if (presorted) {
_indexPaths = indexPaths;
@ -387,9 +402,9 @@
NSNumber *sectionKey = @(indexPath.section);
NSMutableIndexSet *indexSet = sectionToIndexSetMap[sectionKey];
if (indexSet) {
[indexSet addIndex:indexPath.row];
[indexSet addIndex:indexPath.item];
} else {
indexSet = [NSMutableIndexSet indexSetWithIndex:indexPath.row];
indexSet = [NSMutableIndexSet indexSetWithIndex:indexPath.item];
sectionToIndexSetMap[sectionKey] = indexSet;
}
}
@ -397,7 +412,7 @@
return sectionToIndexSetMap;
}
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)sections
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)ignoredSections
{
if (changes.count < 1) {
return;
@ -411,12 +426,9 @@
// All changed index paths, sorted
NSMutableArray *allIndexPaths = [NSMutableArray new];
NSPredicate *indexPathInValidSection = [NSPredicate predicateWithBlock:^BOOL(NSIndexPath *indexPath, __unused NSDictionary *_) {
return ![sections containsIndex:indexPath.section];
}];
for (_ASHierarchyItemChange *change in changes) {
for (NSIndexPath *indexPath in change.indexPaths) {
if ([indexPathInValidSection evaluateWithObject:indexPath]) {
if (![ignoredSections containsIndex:indexPath.section]) {
animationOptions[indexPath] = @(change.animationOptions);
[allIndexPaths addObject:indexPath];
}
@ -459,4 +471,9 @@
[changes setArray:result];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: anim=%lu, type=%@, indexPaths=%@>", NSStringFromClass(self.class), (unsigned long)_animationOptions, NSStringFromASHierarchyChangeType(_changeType), self.indexPaths];
}
@end

View File

@ -8,6 +8,7 @@
@import XCTest;
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "ASTableViewInternal.h"
// Set to 1 to use UITableView and see if the issue still exists.
#define USE_UIKIT_REFERENCE 0
@ -19,8 +20,8 @@
#define TableView ASTableView
#endif
#define kInitialSectionCount 20
#define kInitialItemCount 20
#define kInitialSectionCount 10
#define kInitialItemCount 10
#define kMinimumItemCount 5
#define kMinimumSectionCount 3
#define kFickleness 0.1
@ -145,7 +146,7 @@ static volatile int32_t ASThrashTestSectionNextID = 1;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<Section %lu: itemCount=%lu>", (unsigned long)_sectionID, (unsigned long)self.items.count];
return [NSString stringWithFormat:@"<Section %lu: itemCount=%lu, items=%@>", (unsigned long)_sectionID, (unsigned long)self.items.count, ASThrashArrayDescription(self.items)];
}
- (id)copyWithZone:(NSZone *)zone {
@ -445,17 +446,22 @@ static NSInteger ASThrashUpdateCurrentSerializationVersion = 1;
@implementation ASTableViewThrashTests {
// The current update, which will be logged in case of a failure.
ASThrashUpdate *_update;
BOOL _failed;
}
#pragma mark Overrides
- (void)tearDown {
if (_failed && _update != nil) {
NSLog(@"Failed update %@: %@", _update, _update.logFriendlyBase64Representation);
}
_failed = NO;
_update = nil;
}
// NOTE: Despite the documentation, this is not always called if an exception is caught.
- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected {
[self logCurrentUpdateIfNeeded];
_failed = YES;
[super recordFailureWithDescription:description inFile:filePath atLine:lineNumber expected:expected];
}
@ -477,11 +483,12 @@ static NSInteger ASThrashUpdateCurrentSerializationVersion = 1;
}
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initWithData:_update.oldData];
ds.tableView.test_enableSuperUpdateCallLogging = YES;
[self applyUpdate:_update toDataSource:ds];
[self verifyDataSource:ds];
}
- (void)DISABLED_testThrashingWildly {
- (void)testThrashingWildly {
for (NSInteger i = 0; i < kThrashingIterationCount; i++) {
[self setUp];
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initWithData:[ASThrashTestSection sectionsWithCount:kInitialSectionCount]];
@ -495,12 +502,6 @@ static NSInteger ASThrashUpdateCurrentSerializationVersion = 1;
#pragma mark Helpers
- (void)logCurrentUpdateIfNeeded {
if (_update != nil) {
NSLog(@"Failed update %@: %@", _update, _update.logFriendlyBase64Representation);
}
}
- (void)applyUpdate:(ASThrashUpdate *)update toDataSource:(ASThrashDataSource *)dataSource {
TableView *tableView = dataSource.tableView;
@ -533,7 +534,7 @@ static NSInteger ASThrashUpdateCurrentSerializationVersion = 1;
[tableView waitUntilAllUpdatesAreCommitted];
#endif
} @catch (NSException *exception) {
[self logCurrentUpdateIfNeeded];
_failed = YES;
@throw exception;
}
}
@ -553,7 +554,7 @@ static NSInteger ASThrashUpdateCurrentSerializationVersion = 1;
XCTAssertEqual([tableView rectForRowAtIndexPath:indexPath].size.height, item.rowHeight);
#else
ASThrashTestNode *node = (ASThrashTestNode *)[tableView nodeForRowAtIndexPath:indexPath];
XCTAssertEqual(node.item, item);
XCTAssertEqualObjects(node.item, item, @"Wrong node at index path %@", indexPath);
#endif
}
}

File diff suppressed because one or more lines are too long