From 3059c5e6f5aeac73f112375d032677ae5f38342a Mon Sep 17 00:00:00 2001 From: Ryan Nystrom Date: Thu, 26 Apr 2018 08:05:21 -0700 Subject: [PATCH] Add experiment to defer requesting objects from data source until just before diffing Summary: Inspired by recent performance investigations in Instagram, adding an experimental feature to defer requesting objects from the `IGListAdapterDataSource` until //just before// diffing is executed. If //n// updates are coalesced into one, this results in just a single request for objects from the data source. This is a **breaking change** to the public API, but since writing custom `IGListUpdatingDelegate`s is discouraged, I'm less worried about saving this for a major version launch. Reviewed By: manicakes Differential Revision: D7744518 fbshipit-source-id: f0001263cddda383e3dedd1d350a83d2cfb8362a --- CHANGELOG.md | 4 + Source/Common/IGListExperiments.h | 2 + Source/IGListAdapter.m | 22 ++- Source/IGListAdapterUpdater.m | 38 ++--- Source/IGListReloadDataUpdater.m | 7 +- Source/IGListUpdatingDelegate.h | 8 +- .../IGListAdapterUpdater+DebugDescription.m | 4 +- .../Internal/IGListAdapterUpdaterInternal.h | 2 +- Tests/IGListAdapterUpdaterTests.m | 144 ++++++++++-------- 9 files changed, 142 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc19c2cd7..6699caf63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag 4.0.0 (upcoming release) ----- +### Enhancements + +- Experimental performance improvement from deferring `-[IGListAdapterDataSource objectsForListAdapter:]` calls until just before diffing. [Ryan Nystrom](https://github.com/rnystrom) (tbd) + 3.3.0 ----- diff --git a/Source/Common/IGListExperiments.h b/Source/Common/IGListExperiments.h index cc61ccaf1..8a85839dc 100644 --- a/Source/Common/IGListExperiments.h +++ b/Source/Common/IGListExperiments.h @@ -26,6 +26,8 @@ typedef NS_OPTIONS (NSInteger, IGListExperiment) { IGListExperimentFasterVisibleSectionController = 1 << 4, /// Test deduping item-level updates. IGListExperimentDedupeItemUpdates = 1 << 5, + /// Test deferring object creation until just before diffing. + IGListExperimentDeferredToObjectCreation = 1 << 6, }; /** diff --git a/Source/IGListAdapter.m b/Source/IGListAdapter.m index be3a321c8..a83254c35 100644 --- a/Source/IGListAdapter.m +++ b/Source/IGListAdapter.m @@ -324,14 +324,28 @@ - (void)performUpdatesAnimated:(BOOL)animated completion:(IGListUpdaterCompletio } NSArray *fromObjects = self.sectionMap.objects; - NSArray *newObjects = [dataSource objectsForListAdapter:self]; - - [self _enterBatchUpdates]; + IGListToObjectBlock toObjectsBlock; __weak __typeof__(self) weakSelf = self; + if (IGListExperimentEnabled(self.experiments, IGListExperimentDeferredToObjectCreation)) { + toObjectsBlock = ^NSArray *{ + __typeof__(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return nil; + } + return [dataSource objectsForListAdapter:strongSelf]; + }; + } else { + NSArray *newObjects = [dataSource objectsForListAdapter:self]; + toObjectsBlock = ^NSArray *{ + return newObjects; + }; + } + + [self _enterBatchUpdates]; [self.updater performUpdateWithCollectionView:collectionView fromObjects:fromObjects - toObjects:newObjects + toObjectsBlock:toObjectsBlock animated:animated objectTransitionBlock:^(NSArray *toObjects) { // temporarily capture the item map that we are transitioning from in case diff --git a/Source/IGListAdapterUpdater.m b/Source/IGListAdapterUpdater.m index 22f5a6130..f762adc18 100644 --- a/Source/IGListAdapterUpdater.m +++ b/Source/IGListAdapterUpdater.m @@ -41,7 +41,7 @@ - (BOOL)hasChanges { return self.hasQueuedReloadData || [self.batchUpdates hasChanges] || self.fromObjects != nil - || self.toObjects != nil; + || self.toObjectsBlock != nil; } - (void)performReloadDataWithCollectionView:(UICollectionView *)collectionView { @@ -106,12 +106,25 @@ - (void)performBatchUpdatesWithCollectionView:(UICollectionView *)collectionView // 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]; - NSArray *toObjects = objectsWithDuplicateIdentifiersRemoved(self.toObjects); + IGListToObjectBlock toObjectsBlock = [self.toObjectsBlock copy]; NSMutableArray *completionBlocks = [self.completionBlocks mutableCopy]; void (^objectTransitionBlock)(NSArray *) = [self.objectTransitionBlock copy]; const BOOL animated = self.queuedUpdateIsAnimated; IGListBatchUpdates *batchUpdates = self.batchUpdates; + NSArray *toObjects = nil; + if (toObjectsBlock != nil) { + toObjects = objectsWithDuplicateIdentifiersRemoved(toObjectsBlock()); + } +#ifdef DEBUG + for (id obj in toObjects) { + IGAssert([obj conformsToProtocol:@protocol(IGListDiffable)], + @"In order to use IGListAdapterUpdater, object %@ must conform to IGListDiffable", obj); + IGAssert([obj diffIdentifier] != nil, + @"Cannot have a nil diffIdentifier for object %@", obj); + } +#endif + // clean up all state so that new updates can be coalesced while the current update is in flight [self cleanStateBeforeUpdates]; @@ -340,7 +353,7 @@ - (void)cleanStateBeforeUpdates { // destroy to/from transition items self.fromObjects = nil; - self.toObjects = nil; + self.toObjectsBlock = nil; // destroy reloadData state self.reloadUpdates = nil; @@ -408,11 +421,11 @@ - (NSPointerFunctions *)objectLookupPointerFunctions { } - (void)performUpdateWithCollectionView:(UICollectionView *)collectionView - fromObjects:(nullable NSArray *)fromObjects - toObjects:(nullable NSArray *)toObjects + fromObjects:(NSArray *)fromObjects + toObjectsBlock:(IGListToObjectBlock)toObjectsBlock animated:(BOOL)animated - objectTransitionBlock:(void (^)(NSArray *))objectTransitionBlock - completion:(nullable void (^)(BOOL))completion { + objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock + completion:(IGListUpdatingCompletion)completion { IGAssertMainThread(); IGParameterAssert(collectionView != nil); IGParameterAssert(objectTransitionBlock != nil); @@ -423,21 +436,12 @@ - (void)performUpdateWithCollectionView:(UICollectionView *)collectionView // 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.toObjects = toObjects; + self.toObjectsBlock = toObjectsBlock; // disabled animations will always take priority // reset to YES in -cleanupState self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated; -#ifdef DEBUG - for (id obj in toObjects) { - IGAssert([obj conformsToProtocol:@protocol(IGListDiffable)], - @"In order to use IGListAdapterUpdater, object %@ must conform to IGListDiffable", obj); - IGAssert([obj diffIdentifier] != nil, - @"Cannot have a nil diffIdentifier for object %@", obj); - } -#endif - // always use the last update block, even though this should always do the exact same thing self.objectTransitionBlock = objectTransitionBlock; diff --git a/Source/IGListReloadDataUpdater.m b/Source/IGListReloadDataUpdater.m index 1d73bc3c6..8858f7329 100644 --- a/Source/IGListReloadDataUpdater.m +++ b/Source/IGListReloadDataUpdater.m @@ -19,11 +19,14 @@ - (NSPointerFunctions *)objectLookupPointerFunctions { - (void)performUpdateWithCollectionView:(UICollectionView *)collectionView fromObjects:(NSArray *)fromObjects - toObjects:(NSArray *)toObjects + toObjectsBlock:(IGListToObjectBlock)toObjectsBlock animated:(BOOL)animated objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock completion:(IGListUpdatingCompletion)completion { - objectTransitionBlock(toObjects); + if (toObjectsBlock != nil) { + NSArray *toObjects = toObjectsBlock() ?: @[]; + objectTransitionBlock(toObjects); + } [self _synchronousReloadDataWithCollectionView:collectionView]; if (completion) { completion(YES); diff --git a/Source/IGListUpdatingDelegate.h b/Source/IGListUpdatingDelegate.h index cb0ca30bc..eac0758bc 100644 --- a/Source/IGListUpdatingDelegate.h +++ b/Source/IGListUpdatingDelegate.h @@ -37,6 +37,10 @@ typedef void (^IGListItemUpdateBlock)(void); NS_SWIFT_NAME(ListReloadUpdateBlock) typedef void (^IGListReloadUpdateBlock)(void); +/// A block that returns an array of objects to transition to. +NS_SWIFT_NAME(ListToObjectBlock) +typedef NSArray * _Nullable (^IGListToObjectBlock)(void); + /** Implement this protocol in order to handle both section and row based update events. Implementation should forward or coalesce these events to a backing store or collection. @@ -63,7 +67,7 @@ NS_SWIFT_NAME(ListUpdatingDelegate) @param collectionView The collection view to perform the transition on. @param fromObjects The previous objects in the collection view. Objects must conform to `IGListDiffable`. - @param toObjects The new objects in collection view. Objects must conform to `IGListDiffable`. + @param toObjectsBlock A block returning the new objects in the collection view. Objects must conform to `IGListDiffable`. @param animated A flag indicating if the transition should be animated. @param objectTransitionBlock A block that must be called when the adapter applies changes to the collection view. @param completion A completion block to execute when the update is finished. @@ -77,7 +81,7 @@ NS_SWIFT_NAME(ListUpdatingDelegate) */ - (void)performUpdateWithCollectionView:(UICollectionView *)collectionView fromObjects:(nullable NSArray> *)fromObjects - toObjects:(nullable NSArray> *)toObjects + toObjectsBlock:(nullable IGListToObjectBlock)toObjectsBlock animated:(BOOL)animated objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock completion:(nullable IGListUpdatingCompletion)completion; diff --git a/Source/Internal/IGListAdapterUpdater+DebugDescription.m b/Source/Internal/IGListAdapterUpdater+DebugDescription.m index 22553d40b..bf8a2c007 100644 --- a/Source/Internal/IGListAdapterUpdater+DebugDescription.m +++ b/Source/Internal/IGListAdapterUpdater+DebugDescription.m @@ -61,9 +61,9 @@ @implementation IGListAdapterUpdater (DebugDescription) [debug addObjectsFromArray:IGListDebugIndentedLines(linesFromObjects(self.fromObjects))]; } - if (self.toObjects != nil) { + if (self.toObjectsBlock != nil) { [debug addObject:@"To objects:"]; - [debug addObjectsFromArray:IGListDebugIndentedLines(linesFromObjects(self.toObjects))]; + [debug addObjectsFromArray:IGListDebugIndentedLines(linesFromObjects(self.toObjectsBlock()))]; } if (self.pendingTransitionToObjects != nil) { diff --git a/Source/Internal/IGListAdapterUpdaterInternal.h b/Source/Internal/IGListAdapterUpdaterInternal.h index 6295c1f0b..5fc9e729b 100644 --- a/Source/Internal/IGListAdapterUpdaterInternal.h +++ b/Source/Internal/IGListAdapterUpdaterInternal.h @@ -27,7 +27,7 @@ FOUNDATION_EXTERN void convertReloadToDeleteInsert(NSMutableIndexSet *reloads, @interface IGListAdapterUpdater () @property (nonatomic, copy, nullable) NSArray *fromObjects; -@property (nonatomic, copy, nullable) NSArray *toObjects; +@property (nonatomic, copy, nullable) IGListToObjectBlock toObjectsBlock; @property (nonatomic, copy, nullable) NSArray *pendingTransitionToObjects; @property (nonatomic, strong) NSMutableArray *completionBlocks; diff --git a/Tests/IGListAdapterUpdaterTests.m b/Tests/IGListAdapterUpdaterTests.m index 81bad5b2e..a163f7e52 100644 --- a/Tests/IGListAdapterUpdaterTests.m +++ b/Tests/IGListAdapterUpdaterTests.m @@ -58,27 +58,27 @@ - (void)tearDown { } - (void)test_whenUpdatingWithNil_thatUpdaterHasNoChanges { - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjects:nil animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjectsBlock:nil animated:YES objectTransitionBlock:self.updateBlock completion:nil]; XCTAssertFalse([self.updater hasChanges]); } - (void)test_whenUpdatingtoObjects_thatUpdaterHasChanges { - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjects:@[@0] animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjectsBlock:^NSArray *{return @[@0];} animated:YES objectTransitionBlock:self.updateBlock completion:nil]; XCTAssertTrue([self.updater hasChanges]); } - (void)test_whenUpdatingfromObjects_thatUpdaterHasChanges { - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:@[@0] toObjects:nil animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:@[@0] toObjectsBlock:^NSArray *{return nil;} animated:YES objectTransitionBlock:self.updateBlock completion:nil]; XCTAssertTrue([self.updater hasChanges]); } - (void)test_whenUpdatingtoObjects_withfromObjects_thatUpdaterHasChanges { - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:@[@0] toObjects:@[@1] animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:@[@0] toObjectsBlock:^NSArray *{return @[@1];} animated:YES objectTransitionBlock:self.updateBlock completion:nil]; XCTAssertTrue([self.updater hasChanges]); } - (void)test_whenCleaningUpState_withChanges_thatUpdaterHasNoChanges { - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjects:@[@0] animated:YES objectTransitionBlock:self.updateBlock completion:nil]; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil toObjectsBlock:^NSArray *{return @[@0];} animated:YES objectTransitionBlock:self.updateBlock completion:nil]; XCTAssertTrue([self.updater hasChanges]); [self.updater cleanStateBeforeUpdates]; XCTAssertFalse([self.updater hasChanges]); @@ -97,17 +97,19 @@ - (void)test_whenInsertingSection_thatCollectionViewUpdates { NSArray *from = @[ [IGSectionObject sectionWithObjects:@[]] ]; - NSArray *to = @[ - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]] - ]; + IGListToObjectBlock to = ^NSArray *{ + return @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]] + ]; + }; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionView:self.collectionView]; XCTAssertEqual([self.collectionView numberOfSections], 1); XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 2); [expectation fulfill]; }]; @@ -119,16 +121,18 @@ - (void)test_whenDeletingSection_thatCollectionViewUpdates { [IGSectionObject sectionWithObjects:@[]], [IGSectionObject sectionWithObjects:@[]] ]; - NSArray *to = @[ - [IGSectionObject sectionWithObjects:@[]] - ]; + IGListToObjectBlock to = ^NSArray *{ + return @[ + [IGSectionObject sectionWithObjects:@[]] + ]; + }; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionView:self.collectionView]; XCTAssertEqual([self.collectionView numberOfSections], 2); XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 1); [expectation fulfill]; }]; @@ -139,10 +143,12 @@ - (void)test_whenInsertingSection_withItemChanges_thatCollectionViewUpdates { NSArray *from = @[ [IGSectionObject sectionWithObjects:@[@0]] ]; - NSArray *to = @[ - [IGSectionObject sectionWithObjects:@[@0, @1]], - [IGSectionObject sectionWithObjects:@[@0, @1]] - ]; + IGListToObjectBlock to = ^NSArray *{ + return @[ + [IGSectionObject sectionWithObjects:@[@0, @1]], + [IGSectionObject sectionWithObjects:@[@0, @1]] + ]; + }; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionView:self.collectionView]; @@ -150,7 +156,7 @@ - (void)test_whenInsertingSection_withItemChanges_thatCollectionViewUpdates { XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 2); XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); @@ -164,11 +170,13 @@ - (void)test_whenInsertingSection_withDeletedSection_thatCollectionViewUpdates { [IGSectionObject sectionWithObjects:@[@0, @1, @2]], [IGSectionObject sectionWithObjects:@[]] ]; - NSArray *to = @[ - [IGSectionObject sectionWithObjects:@[@1, @1]], - [IGSectionObject sectionWithObjects:@[@0]], - [IGSectionObject sectionWithObjects:@[@0, @2, @3]] - ]; + IGListToObjectBlock to = ^NSArray *{ + return @[ + [IGSectionObject sectionWithObjects:@[@1, @1]], + [IGSectionObject sectionWithObjects:@[@0]], + [IGSectionObject sectionWithObjects:@[@0, @2, @3]] + ]; + }; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionView:self.collectionView]; @@ -177,7 +185,7 @@ - (void)test_whenInsertingSection_withDeletedSection_thatCollectionViewUpdates { XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3); XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 3); XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2); XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1); @@ -213,9 +221,11 @@ - (void)test_whenCollectionViewNeedsLayout_thatPerformBatchUpdateWorks { [IGSectionObject sectionWithObjects:@[]], [IGSectionObject sectionWithObjects:@[]] ]; - NSArray *to = @[ - [IGSectionObject sectionWithObjects:@[]] - ]; + IGListToObjectBlock to = ^NSArray *{ + return @[ + [IGSectionObject sectionWithObjects:@[]] + ]; + }; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionView:self.collectionView]; @@ -226,7 +236,7 @@ - (void)test_whenCollectionViewNeedsLayout_thatPerformBatchUpdateWorks { [self.collectionView setNeedsLayout]; XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 1); [expectation fulfill]; }]; @@ -237,10 +247,12 @@ - (void)test_whenUpdatesAreReentrant_thatUpdatesExecuteSerially { NSArray *from = @[ [IGSectionObject sectionWithObjects:@[]], ]; - NSArray *to = @[ - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]], - ]; + IGListToObjectBlock to = ^NSArray *{ + return @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]], + ]; + }; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionView:self.collectionView]; @@ -249,12 +261,14 @@ - (void)test_whenUpdatesAreReentrant_thatUpdatesExecuteSerially { XCTestExpectation *expectation1 = genExpectation; void (^preUpdateBlock)(void) = ^{ - NSArray *anotherTo = @[ - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]], - [IGSectionObject sectionWithObjects:@[]] - ]; - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:anotherTo animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + NSArray *(^anotherTo)(void) = ^NSArray *{ + return @[ + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]], + [IGSectionObject sectionWithObjects:@[]] + ]; + }; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:anotherTo animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { completionCounter++; XCTAssertEqual([self.collectionView numberOfSections], 3); XCTAssertEqual(completionCounter, 2); @@ -263,7 +277,7 @@ - (void)test_whenUpdatesAreReentrant_thatUpdatesExecuteSerially { }; XCTestExpectation *expectation2 = genExpectation; - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:^(NSArray *toObjects) { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:^(NSArray *toObjects) { // 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 @@ -299,7 +313,7 @@ - (void)test_whenQueueingItemUpdates_withBatchUpdate_thatItemUpdateBlockExecutes [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:nil - toObjects:@[[IGSectionObject sectionWithObjects:@[@1]]] + toObjectsBlock:^NSArray *{return @[[IGSectionObject sectionWithObjects:@[@1]]];} animated:YES objectTransitionBlock:^(NSArray * toObjects) { self.dataSource.sections = toObjects; sectionUpdateBlockExecuted = YES; @@ -322,10 +336,10 @@ - (void)test_whenQueueingItemUpdates_withBatchUpdate_thatItemUpdateBlockExecutes - (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 @@ -333,12 +347,14 @@ - (void)test_whenItemsMoveAndUpdate_thatCollectionViewWorks { [from[0] setObjects:@[@1, @1]]; [from[1] setObjects:@[@1, @1, @1]]; - // rearrange the modified objects - NSArray *to = @[ - from[2], - from[0], - from[1] - ]; + IGListToObjectBlock to = ^NSArray *{ + // rearrange the modified objects + return @[ + from[2], + from[0], + from[1] + ]; + }; self.dataSource.sections = from; [self.updater performReloadDataWithCollectionView:self.collectionView]; @@ -347,7 +363,7 @@ - (void)test_whenItemsMoveAndUpdate_thatCollectionViewWorks { self.updater.movesAsDeletesInserts = YES; XCTestExpectation *expectation = genExpectation; - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjects:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:from toObjectsBlock:to animated:YES objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { XCTAssertEqual([self.collectionView numberOfSections], 3); XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2); @@ -474,10 +490,12 @@ - (void)test_whenCollectionViewNotInWindow_andBackgroundReloadFlag_isSetNO_diffH [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:OCMOCK_ANY collectionView:self.collectionView]; XCTestExpectation *expectation = genExpectation; - NSArray *to = @[ - [IGSectionObject sectionWithObjects:@[]] - ]; - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:self.dataSource.sections toObjects:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + IGListToObjectBlock to = ^NSArray *{ + return @[ + [IGSectionObject sectionWithObjects:@[]] + ]; + }; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:self.dataSource.sections toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -499,10 +517,12 @@ - (void)test_whenCollectionViewNotInWindow_andBackgroundReloadFlag_isDefaultYES_ [[mockDelegate reject] listAdapterUpdater:self.updater didPerformBatchUpdates:OCMOCK_ANY collectionView:self.collectionView]; XCTestExpectation *expectation = genExpectation; - NSArray *to = @[ - [IGSectionObject sectionWithObjects:@[]] - ]; - [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:self.dataSource.sections toObjects:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + IGListToObjectBlock to = ^NSArray *{ + return @[ + [IGSectionObject sectionWithObjects:@[]] + ]; + }; + [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:self.dataSource.sections toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -549,7 +569,7 @@ - (void)test_whenNotInViewHierarchy_thatUpdatesStillExecuteBlocks { __block BOOL completionBlockExecuted = NO; [self.updater performUpdateWithCollectionView:self.collectionView fromObjects:self.dataSource.sections - toObjects:self.dataSource.sections + toObjectsBlock:^NSArray *{return self.dataSource.sections;} animated:YES objectTransitionBlock:^(NSArray *toObjects) { objectTransitionBlockExecuted = YES; @@ -598,3 +618,5 @@ - (void)test_whenObjectIdentifiersCollide_withDifferentTypes_thatLookupReturnsNi } @end + +