From caf058c85a4e3dcc383a44db6a6ea6373c32e362 Mon Sep 17 00:00:00 2001 From: Maxime Ollivier Date: Tue, 8 Sep 2020 09:06:16 -0700 Subject: [PATCH] implement performExperimentalUpdateAnimated Summary: Lets implement `performExperimentalUpdateAnimated`. There's a couple of things happening: * We're now using the `IGListTransitionData` container object * We don't need `pendingTransitionToObjects` anymore because we query the `fromObjects` just before performing the diffing. * We should update the unit tests to use `performExperimentalUpdateAnimated` since the regular `performUpdateAnimated` will now fail. Reviewed By: patters Differential Revision: D23145781 fbshipit-source-id: 0b896e918241e709c5eb23f56a6b7323521a2222 --- Source/IGListKit/IGListAdapter.m | 2 +- .../IGListExperimentalAdapterUpdater.h | 3 +- .../IGListExperimentalAdapterUpdater.m | 71 ++-- ...IGListExperimentalAdapterUpdaterInternal.h | 7 +- Tests/IGListExperimentalAdapterUpdaterTests.m | 364 +++++++++++------- 5 files changed, 260 insertions(+), 187 deletions(-) diff --git a/Source/IGListKit/IGListAdapter.m b/Source/IGListKit/IGListAdapter.m index 7beb8858..b89cfc01 100644 --- a/Source/IGListKit/IGListAdapter.m +++ b/Source/IGListKit/IGListAdapter.m @@ -393,7 +393,7 @@ if (strongSelf == nil) { return nil; } - NSArray *toObjects = [dataSource objectsForListAdapter:strongSelf]; + NSArray *toObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:strongSelf]); return [strongSelf _generateTransitionDataWithObjects:toObjects dataSource:dataSource]; }; diff --git a/Source/IGListKit/IGListExperimentalAdapterUpdater.h b/Source/IGListKit/IGListExperimentalAdapterUpdater.h index 082b012d..1d6a1a37 100644 --- a/Source/IGListKit/IGListExperimentalAdapterUpdater.h +++ b/Source/IGListKit/IGListExperimentalAdapterUpdater.h @@ -9,6 +9,7 @@ #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -24,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN */ IGLK_SUBCLASSING_RESTRICTED NS_SWIFT_NAME(ListExperimentalAdapterUpdater) -@interface IGListExperimentalAdapterUpdater : NSObject +@interface IGListExperimentalAdapterUpdater : NSObject @end diff --git a/Source/IGListKit/IGListExperimentalAdapterUpdater.m b/Source/IGListKit/IGListExperimentalAdapterUpdater.m index 66593635..cab933b3 100644 --- a/Source/IGListKit/IGListExperimentalAdapterUpdater.m +++ b/Source/IGListKit/IGListExperimentalAdapterUpdater.m @@ -11,10 +11,10 @@ #import #import "IGListAdapterUpdaterHelpers.h" -#import "IGListArrayUtilsInternal.h" #import "IGListIndexSetResultInternal.h" #import "IGListMoveIndexPathInternal.h" #import "IGListReloadIndexPath.h" +#import "IGListTransitionData.h" #import "UICollectionView+IGListBatchUpdateData.h" typedef void (^IGListAdapterUpdaterDiffResultBlock)(IGListIndexSetResult *); @@ -50,8 +50,7 @@ typedef void (^IGListAdapterUpdaterCompletionBlock)(BOOL); - (BOOL)hasChanges { return self.hasQueuedReloadData || [self.batchUpdates hasChanges] - || self.fromObjects != nil - || self.toObjectsBlock != nil; + || self.dataBlock != nil; } - (void)performReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock { @@ -118,10 +117,9 @@ typedef void (^IGListAdapterUpdaterCompletionBlock)(BOOL); // create local variables so we can immediately clean our state but pass these items into the batch update block id delegate = self.delegate; - NSArray *fromObjects = [self.fromObjects copy]; - IGListToObjectBlock toObjectsBlock = [self.toObjectsBlock copy]; + IGListTransitionDataBlock dataBlock = [self.dataBlock copy]; + IGListTransitionDataApplyBlock applyDataBlock = [self.applyDataBlock copy]; NSMutableArray *completionBlocks = [self.completionBlocks mutableCopy]; - void (^objectTransitionBlock)(NSArray *) = [self.objectTransitionBlock copy]; const BOOL animated = self.queuedUpdateIsAnimated; const BOOL allowsReloadingOnTooManyUpdates = self.allowsReloadingOnTooManyUpdates; const IGListExperiment experiments = self.experiments; @@ -148,10 +146,15 @@ typedef void (^IGListAdapterUpdaterCompletionBlock)(BOOL); return; } - NSArray *toObjects = nil; - if (toObjectsBlock != nil) { - toObjects = objectsWithDuplicateIdentifiersRemoved(toObjectsBlock()); + + IGListTransitionData *data = nil; + if (dataBlock != nil) { + data = dataBlock(); } + + NSArray *toObjects = data.toObjects; + NSArray *fromObjects = data.fromObjects; + #ifdef DEBUG for (id obj in toObjects) { IGAssert([obj conformsToProtocol:@protocol(IGListDiffable)], @@ -167,8 +170,8 @@ typedef void (^IGListAdapterUpdaterCompletionBlock)(BOOL); // run the update block so that the adapter can set its items. this makes sure that just before the update is // committed that the data source is updated to the /latest/ "toObjects". this makes the data source in sync // with the items that the updater is transitioning to - if (objectTransitionBlock != nil) { - objectTransitionBlock(toObjects); + if (applyDataBlock != nil && data != nil) { + applyDataBlock(data); } // execute each item update block which should make calls like insert, delete, and reload for index paths @@ -189,7 +192,6 @@ typedef void (^IGListAdapterUpdaterCompletionBlock)(BOOL); [delegate listAdapterUpdater:self willReloadDataWithCollectionView:collectionView isFallbackReload:YES]; executeUpdateBlocks(); [self _cleanStateAfterUpdates]; - [self _performBatchUpdatesItemBlockApplied]; [collectionView reloadData]; [collectionView layoutIfNeeded]; executeCompletionBlocks(YES); @@ -201,7 +203,7 @@ typedef void (^IGListAdapterUpdaterCompletionBlock)(BOOL); }; // disables multiple performBatchUpdates: from happening at the same time - [self _beginPerformBatchUpdatesToObjects:toObjects]; + self.state = IGListBatchUpdateStateQueuedBatchUpdate; // if the collection view isn't in a visible window, skip diffing and batch updating. execute all transition blocks, // reload data, execute completion blocks, and get outta here @@ -239,7 +241,6 @@ typedef void (^IGListAdapterUpdaterCompletionBlock)(BOOL); } [self _cleanStateAfterUpdates]; - [self _performBatchUpdatesItemBlockApplied]; }; // block used as the second param of -[UICollectionView performBatchUpdates:completion:] @@ -312,28 +313,18 @@ willPerformBatchUpdatesWithCollectionView:collectionView }); } -- (void)_beginPerformBatchUpdatesToObjects:(NSArray *)toObjects { - self.pendingTransitionToObjects = toObjects; - self.state = IGListBatchUpdateStateQueuedBatchUpdate; -} - -- (void)_performBatchUpdatesItemBlockApplied { - self.pendingTransitionToObjects = nil; -} - - (void)cleanStateBeforeUpdates { self.queuedUpdateIsAnimated = YES; // destroy to/from transition items - self.fromObjects = nil; - self.toObjectsBlock = nil; + self.dataBlock = nil; // destroy reloadData state self.reloadUpdates = nil; self.queuedReloadData = NO; // remove indexpath/item changes - self.objectTransitionBlock = nil; + self.applyDataBlock = nil; // removes all object completion blocks. done before updates to start collecting completion blocks for coalesced // or re-entrant object updates @@ -395,25 +386,30 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons animated:(BOOL)animated objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock completion:(IGListUpdatingCompletion)completion { + IGFailAssert(@"IGListExperimentalAdapterUpdater works with IGListUpdatingDelegateExperimental and doesn't implement the regular -performUpdateWithCollectionViewBlock method"); + completion(NO); +} + +- (void)performExperimentalUpdateAnimated:(BOOL)animated + collectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock + dataBlock:(IGListTransitionDataBlock)dataBlock + applyDataBlock:(IGListTransitionDataApplyBlock)applyDataBlock + completion:(IGListUpdatingCompletion)completion { IGAssertMainThread(); IGParameterAssert(collectionViewBlock != nil); - IGParameterAssert(objectTransitionBlock != nil); + IGParameterAssert(dataBlock != nil); + IGParameterAssert(applyDataBlock != nil); - // only update the items that we are coming from if it has not been set - // this allows multiple updates to be called while an update is already in progress, and the transition from > to - // will be done on the first "fromObjects" received and the last "toObjects" - // if performBatchUpdates: hasn't applied the update block, then data source hasn't transitioned its state. if an - // update is queued in between then we must use the pending toObjects - self.fromObjects = self.fromObjects ?: self.pendingTransitionToObjects ?: fromObjects; - self.toObjectsBlock = toObjectsBlock; + // will call the dataBlock after the dispatch + self.dataBlock = dataBlock; + + // always use the last update block, even though this should always do the exact same thing + self.applyDataBlock = applyDataBlock; // disabled animations will always take priority // reset to YES in -cleanupState self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated; - // always use the last update block, even though this should always do the exact same thing - self.objectTransitionBlock = objectTransitionBlock; - IGListUpdatingCompletion localCompletion = completion; if (localCompletion) { [self.completionBlocks addObject:localCompletion]; @@ -422,6 +418,7 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons [self _queueUpdateWithCollectionViewBlock:collectionViewBlock]; } + - (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock animated:(BOOL)animated itemUpdates:(void (^)(void))itemUpdates diff --git a/Source/IGListKit/Internal/IGListExperimentalAdapterUpdaterInternal.h b/Source/IGListKit/Internal/IGListExperimentalAdapterUpdaterInternal.h index 8131ffcd..7fae5fd4 100644 --- a/Source/IGListKit/Internal/IGListExperimentalAdapterUpdaterInternal.h +++ b/Source/IGListKit/Internal/IGListExperimentalAdapterUpdaterInternal.h @@ -9,6 +9,7 @@ #import #import +#import #import "IGListExperimentalAdapterUpdater.h" #import "IGListBatchUpdateState.h" @@ -18,16 +19,14 @@ NS_ASSUME_NONNULL_BEGIN @interface IGListExperimentalAdapterUpdater () -@property (nonatomic, copy, nullable) NSArray *fromObjects; -@property (nonatomic, copy, nullable) IGListToObjectBlock toObjectsBlock; -@property (nonatomic, copy, nullable) NSArray *pendingTransitionToObjects; +@property (nonatomic, copy, nullable) IGListTransitionDataBlock dataBlock; @property (nonatomic, strong) NSMutableArray *completionBlocks; @property (nonatomic, assign) BOOL queuedUpdateIsAnimated; @property (nonatomic, strong) IGListBatchUpdates *batchUpdates; -@property (nonatomic, copy, nullable) IGListObjectTransitionBlock objectTransitionBlock; +@property (nonatomic, copy, nullable) IGListTransitionDataApplyBlock applyDataBlock; @property (nonatomic, copy, nullable) IGListReloadUpdateBlock reloadUpdates; @property (nonatomic, assign, getter=hasQueuedReloadData) BOOL queuedReloadData; diff --git a/Tests/IGListExperimentalAdapterUpdaterTests.m b/Tests/IGListExperimentalAdapterUpdaterTests.m index 16e98e2a..2e9d26e4 100644 --- a/Tests/IGListExperimentalAdapterUpdaterTests.m +++ b/Tests/IGListExperimentalAdapterUpdaterTests.m @@ -15,6 +15,7 @@ #import "IGListExperimentalAdapterUpdaterInternal.h" #import "IGListMoveIndexInternal.h" #import "IGListTestUICollectionViewDataSource.h" +#import "IGListTransitionData.h" #import "IGTestObject.h" #define genExpectation [self expectationWithDescription:NSStringFromSelector(_cmd)] @@ -27,7 +28,7 @@ @property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, strong) IGListTestUICollectionViewDataSource *dataSource; @property (nonatomic, strong) IGListExperimentalAdapterUpdater *updater; -@property (nonatomic, strong) IGListObjectTransitionBlock updateBlock; +@property (nonatomic, strong) IGListTransitionDataApplyBlock applyDataBlock; @end @@ -37,6 +38,12 @@ return ^UICollectionView *{ return self.collectionView; }; } +- (IGListTransitionDataBlock)dataBlockFromObjects:(NSArray *)fromObjects toObjects:(NSArray *)toObjects { + return ^IGListTransitionData *{ + return [[IGListTransitionData alloc] initFromObjects:fromObjects toObjects:toObjects toSectionControllers:@[]]; + }; +} + - (void)setUp { [super setUp]; @@ -50,8 +57,8 @@ self.dataSource = [[IGListTestUICollectionViewDataSource alloc] initWithCollectionView:self.collectionView]; self.updater = [IGListExperimentalAdapterUpdater new]; __weak __typeof__(self) weakSelf = self; - self.updateBlock = ^(NSArray *obj) { - weakSelf.dataSource.sections = obj; + self.applyDataBlock = ^(IGListTransitionData *data) { + weakSelf.dataSource.sections = data.toObjects; }; } @@ -63,28 +70,39 @@ self.updater = nil; } -- (void)test_whenUpdatingWithNil_thatUpdaterHasNoChanges { - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:nil toObjectsBlock:nil animated:YES objectTransitionBlock:self.updateBlock completion:nil]; - XCTAssertFalse([self.updater hasChanges]); -} - - (void)test_whenUpdatingtoObjects_thatUpdaterHasChanges { - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:nil toObjectsBlock:^NSArray *{return @[@0];} animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:@[] toObjects:@[@0]] + applyDataBlock:self.applyDataBlock + completion:nil]; XCTAssertTrue([self.updater hasChanges]); } - (void)test_whenUpdatingfromObjects_thatUpdaterHasChanges { - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:@[@0] toObjectsBlock:^NSArray *{return nil;} animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:@[@0] toObjects:@[]] + applyDataBlock:self.applyDataBlock + completion:nil]; XCTAssertTrue([self.updater hasChanges]); } - (void)test_whenUpdatingtoObjects_withfromObjects_thatUpdaterHasChanges { - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:@[@0] toObjectsBlock:^NSArray *{return @[@1];} animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:@[@0] toObjects:@[@1]] + applyDataBlock:self.applyDataBlock + completion:nil]; XCTAssertTrue([self.updater hasChanges]); } - (void)test_whenCleaningUpState_withChanges_thatUpdaterHasNoChanges { - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:nil toObjectsBlock:^NSArray *{return @[@0];} animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:@[] toObjects:@[@0]] + applyDataBlock:self.applyDataBlock + completion:nil]; XCTAssertTrue([self.updater hasChanges]); [self.updater cleanStateBeforeUpdates]; XCTAssertFalse([self.updater hasChanges]); @@ -112,21 +130,23 @@ - (void)test_whenInsertingSection_thatCollectionViewUpdates { NSArray *from = @[ - [IGSectionObject sectionWithObjects:@[]] - ]; - IGListToObjectBlock to = ^NSArray *{ - return @[ - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]] - ]; - }; + [IGSectionObject sectionWithObjects:@[]] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]] + ]; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]]; XCTAssertEqual([self.collectionView numberOfSections], 1); XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 2); [expectation fulfill]; }]; @@ -135,21 +155,23 @@ - (void)test_whenDeletingSection_thatCollectionViewUpdates { NSArray *from = @[ - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]] - ]; - IGListToObjectBlock to = ^NSArray *{ - return @[ - [IGSectionObject sectionWithObjects:@[]] - ]; - }; + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[]] + ]; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]]; XCTAssertEqual([self.collectionView numberOfSections], 2); XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 1); [expectation fulfill]; }]; @@ -158,14 +180,12 @@ - (void)test_whenInsertingSection_withItemChanges_thatCollectionViewUpdates { NSArray *from = @[ - [IGSectionObject sectionWithObjects:@[@0]] - ]; - IGListToObjectBlock to = ^NSArray *{ - return @[ - [IGSectionObject sectionWithObjects:@[@0, @1]], - [IGSectionObject sectionWithObjects:@[@0, @1]] - ]; - }; + [IGSectionObject sectionWithObjects:@[@0]] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[@0, @1]], + [IGSectionObject sectionWithObjects:@[@0, @1]] + ]; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]]; @@ -173,7 +193,11 @@ XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 2); XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); @@ -184,16 +208,14 @@ - (void)test_whenInsertingSection_withDeletedSection_thatCollectionViewUpdates { NSArray *from = @[ - [IGSectionObject sectionWithObjects:@[@0, @1, @2]], - [IGSectionObject sectionWithObjects:@[]] - ]; - IGListToObjectBlock to = ^NSArray *{ - return @[ - [IGSectionObject sectionWithObjects:@[@1, @1]], - [IGSectionObject sectionWithObjects:@[@0]], - [IGSectionObject sectionWithObjects:@[@0, @2, @3]] - ]; - }; + [IGSectionObject sectionWithObjects:@[@0, @1, @2]], + [IGSectionObject sectionWithObjects:@[]] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[@1, @1]], + [IGSectionObject sectionWithObjects:@[@0]], + [IGSectionObject sectionWithObjects:@[@0, @2, @3]] + ]; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]]; @@ -202,7 +224,11 @@ XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 3); XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1); @@ -235,14 +261,12 @@ - (void)test_whenCollectionViewNeedsLayout_thatPerformBatchUpdateWorks { NSArray *from = @[ - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]] - ]; - IGListToObjectBlock to = ^NSArray *{ - return @[ - [IGSectionObject sectionWithObjects:@[]] - ]; - }; + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[]] + ]; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]]; @@ -253,7 +277,11 @@ [self.collectionView setNeedsLayout]; XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 1); [expectation fulfill]; }]; @@ -262,14 +290,12 @@ - (void)test_whenUpdatesAreReentrant_thatUpdatesExecuteSerially { NSArray *from = @[ - [IGSectionObject sectionWithObjects:@[]], - ]; - IGListToObjectBlock to = ^NSArray *{ - return @[ - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]], - ]; - }; + [IGSectionObject sectionWithObjects:@[]], + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]], + ]; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]]; @@ -278,14 +304,16 @@ XCTestExpectation *expectation1 = genExpectation; void (^preUpdateBlock)(void) = ^{ - NSArray *(^anotherTo)(void) = ^NSArray *{ - return @[ - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]] - ]; - }; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:anotherTo animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + NSArray *anotherTo = @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]] + ]; + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:to toObjects:anotherTo] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { completionCounter++; XCTAssertEqual([self.collectionView numberOfSections], 3); XCTAssertEqual(completionCounter, 2); @@ -294,14 +322,18 @@ }; XCTestExpectation *expectation2 = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:^(NSArray *toObjects) { + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:^(IGListTransitionData *data) { // executing this block within the updater is just before performBatchUpdates: are applied // should be able to queue another update here, similar to an update being queued between it beginning and executing // the performBatchUpdates: block preUpdateBlock(); - self.dataSource.sections = toObjects; - } completion:^(BOOL finished) { + self.dataSource.sections = data.toObjects; + } + completion:^(BOOL finished) { completionCounter++; XCTAssertEqual([self.collectionView numberOfSections], 2); XCTAssertEqual(completionCounter, 1); @@ -328,14 +360,14 @@ __block BOOL itemUpdateBlockExecuted = NO; __block BOOL sectionUpdateBlockExecuted = NO; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] - fromObjects:nil - toObjectsBlock:^NSArray *{return @[[IGSectionObject sectionWithObjects:@[@1]]];} - animated:YES objectTransitionBlock:^(NSArray * toObjects) { - self.dataSource.sections = toObjects; - sectionUpdateBlockExecuted = YES; - } - completion:nil]; + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:@[] toObjects:@[[IGSectionObject sectionWithObjects:@[@1]]]] + applyDataBlock:^(IGListTransitionData * data) { + self.dataSource.sections = data.toObjects; + sectionUpdateBlockExecuted = YES; + } + completion:nil]; XCTestExpectation *expectation = genExpectation; [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] animated:YES itemUpdates:^{ @@ -353,10 +385,10 @@ - (void)test_whenItemsMoveAndUpdate_thatCollectionViewWorks { NSArray *from = @[ - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]], - ]; + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]], + ]; // change the number of items in the section, which a move would be unable to handle and would throw // keep the same pointers so that the objects are equal @@ -364,14 +396,11 @@ [from[0] setObjects:@[@1, @1]]; [from[1] setObjects:@[@1, @1, @1]]; - IGListToObjectBlock to = ^NSArray *{ - // rearrange the modified objects - return @[ - from[2], - from[0], - from[1] - ]; - }; + NSArray *to = @[ + from[2], + from[0], + from[1] + ]; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]]; @@ -380,7 +409,11 @@ self.updater.sectionMovesAsDeletesInserts = YES; XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 3); XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); @@ -508,7 +541,11 @@ [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:OCMOCK_ANY collectionView:self.collectionView]; XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:self.dataSource.sections toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:self.dataSource.sections toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -526,12 +563,14 @@ [[mockDelegate expect] listAdapterUpdater:self.updater didReloadDataWithCollectionView:self.collectionView isFallbackReload:YES]; XCTestExpectation *expectation = genExpectation; - IGListToObjectBlock to = ^NSArray *{ - return @[ - [IGSectionObject sectionWithObjects:@[]] - ]; - }; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:self.dataSource.sections toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[]] + ]; + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:self.dataSource.sections toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -549,13 +588,15 @@ [[mockDelegate expect] listAdapterUpdater:self.updater didReloadDataWithCollectionView:self.collectionView isFallbackReload:YES]; XCTestExpectation *expectation = genExpectation; - IGListToObjectBlock to = ^NSArray *{ - return @[ - [IGSectionObject sectionWithObjects:@[]] - ]; - }; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[]] + ]; self.collectionView.dataSource = nil; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:self.dataSource.sections toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:self.dataSource.sections toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -572,7 +613,8 @@ }]; XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] animated:YES itemUpdates:^{ + [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] + animated:YES itemUpdates:^{ object.objects = @[@2, @1, @4, @5]; [self.updater insertItemsIntoCollectionView:self.collectionView indexPaths:@[ [NSIndexPath indexPathForItem:2 inSection:0], @@ -600,16 +642,15 @@ __block BOOL objectTransitionBlockExecuted = NO; __block BOOL completionBlockExecuted = NO; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] - fromObjects:self.dataSource.sections - toObjectsBlock:^NSArray *{return self.dataSource.sections;} - animated:YES - objectTransitionBlock:^(NSArray *toObjects) { - objectTransitionBlockExecuted = YES; - } - completion:^(BOOL finished) { - completionBlockExecuted = YES; - }]; + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:self.dataSource.sections toObjects:self.dataSource.sections] + applyDataBlock:^(IGListTransitionData *data) { + objectTransitionBlockExecuted = YES; + } + completion:^(BOOL finished) { + completionBlockExecuted = YES; + }]; XCTestExpectation *expectation = genExpectation; [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] animated:YES itemUpdates:^{ @@ -712,7 +753,11 @@ XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -734,8 +779,12 @@ // Manually set the data source to be nil. self->_collectionView.dataSource = nil; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:^(NSArray * _Nonnull toObjects) { - } completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:^(IGListTransitionData *data) { + } + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -785,23 +834,25 @@ [IGSectionObject sectionWithObjects:@[@0, @1]], [IGSectionObject sectionWithObjects:@[@0, @1]] ]; - IGListToObjectBlock toObjectsBlock2 = ^NSArray *{ - return objects2; - }; - IGListToObjectBlock toObjectsBlock3 = ^NSArray *{ - return objects3; - }; self.dataSource.sections = objects1; [self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]]; XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:objects1 toObjectsBlock:toObjectsBlock2 animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:objects1 toObjects:objects2] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 2); XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:objects2 toObjectsBlock:toObjectsBlock3 animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished2) { + [self.updater performExperimentalUpdateAnimated:YES + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:objects2 toObjects:objects3] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished2) { XCTAssertEqual([self.collectionView numberOfSections], 3); XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); @@ -822,9 +873,6 @@ [IGSectionObject sectionWithObjects:@[] identifier:@"0"], [IGSectionObject sectionWithObjects:@[] identifier:@"1"] ]; - IGListToObjectBlock toBlock = ^NSArray *{ - return to; - }; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]]; @@ -842,7 +890,11 @@ XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:toBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -876,7 +928,11 @@ XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -908,7 +964,11 @@ XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -946,7 +1006,11 @@ XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -984,7 +1048,11 @@ XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -1024,7 +1092,11 @@ XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -1059,7 +1131,11 @@ XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performExperimentalUpdateAnimated:NO + collectionViewBlock:[self collectionViewBlock] + dataBlock:[self dataBlockFromObjects:from toObjects:to] + applyDataBlock:self.applyDataBlock + completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation;