diff --git a/CHANGELOG.md b/CHANGELOG.md index eb3b96ab..454a95e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag - All `IGListBindingSectionControllerSelectionDelegate` methods are now required. [Bofei Zhu] (https://github.com/zhubofei) [(#1186)](https://github.com/Instagram/IGListKit/pull/1186) +- Renamed `[IGListAdapterUpdatingDelegate listAdapterUpdater:willPerformBatchUpdatesWithCollectionView:]` to `[IGListAdapterUpdatingDelegate listAdapterUpdater:willPerformBatchUpdatesWithCollectionView:fromObjects:toObjects:listIndexSetResult:]` to include more supporting info on updated objects. [Jeremy Cohen] (https://github.com/jeremycohen) (tbd) + ### Enhancements - Added `IGListCollectionScrollingTraits` for exposing `UICollectionView` scrolling traits to section controllers via `IGListCollectionContext`. [Adam Stern](https://github.com/adamastern) (tbd) diff --git a/Source/IGListAdapterUpdater.m b/Source/IGListAdapterUpdater.m index e6e00ce2..2b65bf1e 100644 --- a/Source/IGListAdapterUpdater.m +++ b/Source/IGListAdapterUpdater.m @@ -228,7 +228,11 @@ [collectionView layoutIfNeeded]; @try { - [delegate listAdapterUpdater:self willPerformBatchUpdatesWithCollectionView:collectionView]; + [delegate listAdapterUpdater:self +willPerformBatchUpdatesWithCollectionView:collectionView + fromObjects:fromObjects + toObjects:toObjects + listIndexSetResult:result]; if (result.changeCount > 100 && IGListExperimentEnabled(experiments, IGListExperimentReloadDataFallback)) { reloadDataFallback(); } else if (animated) { diff --git a/Source/IGListAdapterUpdaterDelegate.h b/Source/IGListAdapterUpdaterDelegate.h index bd88f729..1d8059d8 100644 --- a/Source/IGListAdapterUpdaterDelegate.h +++ b/Source/IGListAdapterUpdaterDelegate.h @@ -10,6 +10,9 @@ #import @class IGListAdapterUpdater; +@class IGListBatchUpdates; +@class IGListIndexSetResult; +@protocol IGListDiffable; NS_ASSUME_NONNULL_BEGIN @@ -24,8 +27,15 @@ NS_SWIFT_NAME(ListAdapterUpdaterDelegate) @param listAdapterUpdater The adapter updater owning the transition. @param collectionView The collection view that will perform the batch updates. + @param fromObjects The items transitioned from in the batch updates, if any. + @param toObjects The items transitioned to in the batch updates, if any. + @param listIndexSetResults The diffing result of indices to be inserted/removed/updated/moved/etc. */ -- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater willPerformBatchUpdatesWithCollectionView:(UICollectionView *)collectionView; +- (void) listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater +willPerformBatchUpdatesWithCollectionView:(UICollectionView *)collectionView + fromObjects:(nullable NSArray > *)fromObjects + toObjects:(nullable NSArray > *)toObjects + listIndexSetResult:(nullable IGListIndexSetResult *)listIndexSetResults; /** Notifies the delegate that the updater successfully finished `-[UICollectionView performBatchUpdates:completion:]`. diff --git a/Tests/IGListAdapterUpdaterTests.m b/Tests/IGListAdapterUpdaterTests.m index b492e108..0d2c26f7 100644 --- a/Tests/IGListAdapterUpdaterTests.m +++ b/Tests/IGListAdapterUpdaterTests.m @@ -17,6 +17,7 @@ #define genExpectation [self expectationWithDescription:NSStringFromSelector(_cmd)] #define waitExpectation [self waitForExpectationsWithTimeout:30 handler:nil] +#define genToBlock ^NSArray *{ return to; } @interface IGListAdapterUpdaterTests : XCTestCase @@ -435,7 +436,7 @@ id mockDelegate = [OCMockObject mockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; self.updater.delegate = mockDelegate; id compilerFriendlyNil = nil; - [[mockDelegate reject] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:compilerFriendlyNil]; + [[mockDelegate reject] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:compilerFriendlyNil fromObjects:@[] toObjects:@[] listIndexSetResult:OCMOCK_ANY]; [[mockDelegate reject] listAdapterUpdater:self.updater didPerformBatchUpdates:[OCMArg any] collectionView:compilerFriendlyNil]; [self.updater performBatchUpdatesWithCollectionViewBlock:^UICollectionView *{ return compilerFriendlyNil; }]; [mockDelegate verify]; @@ -489,16 +490,14 @@ id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; self.updater.delegate = mockDelegate; [mockDelegate setExpectationOrderMatters:YES]; - [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; + NSArray *to = @[[IGSectionObject sectionWithObjects:@[]]]; + [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView fromObjects:self.dataSource.sections toObjects:to listIndexSetResult:[OCMArg checkWithBlock:^BOOL(IGListIndexSetResult *result) { + return result.inserts.firstIndex == 0 && result.moves.count == 0 && result.updates.count == 0 && result.deletes.count == 0; + }]]; [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:OCMOCK_ANY collectionView:self.collectionView]; 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) { + [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:self.dataSource.sections toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -516,7 +515,7 @@ // in the future, but we configure the mock delegate to allow any call // except the batch updates calls. - [[mockDelegate reject] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; + [[mockDelegate reject] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView fromObjects:@[] toObjects:@[] listIndexSetResult:OCMOCK_ANY]; [[mockDelegate reject] listAdapterUpdater:self.updater didPerformBatchUpdates:OCMOCK_ANY collectionView:self.collectionView]; XCTestExpectation *expectation = genExpectation; @@ -668,22 +667,21 @@ deleteIndexPaths:@[] updateIndexPaths:@[] moveIndexPaths:@[]]; + NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id"]]; + NSArray *to = @[[IGSectionObject sectionWithObjects:@[@2] identifier:@"id"]]; + self.dataSource.sections = from; id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; self.updater.delegate = mockDelegate; [mockDelegate setExpectationOrderMatters:YES]; - [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; + [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView fromObjects:from toObjects:to listIndexSetResult:[OCMArg checkWithBlock:^BOOL(IGListIndexSetResult *result) { + return result.inserts.count == 0 && result.deletes.count == 0 && result.moves.count == 0 && result.updates.firstIndex == 0; + }]]; [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:expectedBatchUpdateData collectionView:self.collectionView]; XCTestExpectation *expectation = genExpectation; - NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id"]]; - IGListToObjectBlock to = ^NSArray *{ - // Update the items - return @[[IGSectionObject sectionWithObjects:@[@2] identifier:@"id"]]; - }; - self.dataSource.sections = from; - - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + + [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -702,22 +700,21 @@ deleteIndexPaths:@[] updateIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]] moveIndexPaths:@[]]; - + NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id"]]; + // Update the items + NSArray *to = @[[IGSectionObject sectionWithObjects:@[@2] identifier:@"id"]]; + self.dataSource.sections = from; id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; self.updater.delegate = mockDelegate; [mockDelegate setExpectationOrderMatters:YES]; - [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; + [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView fromObjects:from toObjects:to listIndexSetResult:[OCMArg checkWithBlock:^BOOL(IGListIndexSetResult *result) { + return result.inserts.count == 0 && result.deletes.count == 0 && result.moves.count == 0 && result.updates.firstIndex == 0; + }]]; [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:expectedBatchUpdateData collectionView:self.collectionView]; XCTestExpectation *expectation = genExpectation; - NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id"]]; - IGListToObjectBlock to = ^NSArray *{ - // Update the items - return @[[IGSectionObject sectionWithObjects:@[@2] identifier:@"id"]]; - }; - self.dataSource.sections = from; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -734,22 +731,22 @@ deleteIndexPaths:@[] updateIndexPaths:@[] moveIndexPaths:@[]]; + NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id"]]; + // more items in the section + NSArray *to = @[[IGSectionObject sectionWithObjects:@[@1, @2] identifier:@"id"]]; + self.dataSource.sections = from; id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; self.updater.delegate = mockDelegate; [mockDelegate setExpectationOrderMatters:YES]; - [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; + [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView fromObjects:from toObjects:to listIndexSetResult:[OCMArg checkWithBlock:^BOOL(IGListIndexSetResult *result) { + return result.inserts.count == 0 && result.deletes.count == 0 && result.moves.count == 0 && result.updates.firstIndex == 0; + }]]; [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:expectedBatchUpdateData collectionView:self.collectionView]; XCTestExpectation *expectation = genExpectation; - NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id"]]; - IGListToObjectBlock to = ^NSArray *{ - // more items in the section - return @[[IGSectionObject sectionWithObjects:@[@1, @2] identifier:@"id"]]; - }; - self.dataSource.sections = from; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -766,23 +763,28 @@ deleteIndexPaths:@[] updateIndexPaths:@[] moveIndexPaths:@[]]; + NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"], + [IGSectionObject sectionWithObjects:@[@2] identifier:@"id2"]]; + // move section, and also update the item for "id2" + NSArray *to = @[[IGSectionObject sectionWithObjects:@[@22] identifier:@"id2"], + [IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"]]; + self.dataSource.sections = from; + id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; self.updater.delegate = mockDelegate; [mockDelegate setExpectationOrderMatters:YES]; - [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; + [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView fromObjects:from toObjects:to listIndexSetResult:[OCMArg checkWithBlock:^BOOL(IGListIndexSetResult *result) { + if (result.inserts.count != 0 || result.deletes.count != 0) { + return NO; + } + // Make sure we note that index 1 is updated (id2 from @[@2] -> @[@22], "id1" moved from section 0 -> 1, and "id2" moved from section 1 -> 0 + return result.updates.firstIndex == 1 && result.moves.count == 2 && [result.moves containsObject:[[IGListMoveIndex alloc] initWithFrom:0 to:1]] && [result.moves containsObject:[[IGListMoveIndex alloc] initWithFrom:1 to:0]]; + }]]; [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:expectedBatchUpdateData collectionView:self.collectionView]; XCTestExpectation *expectation = genExpectation; - NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"], - [IGSectionObject sectionWithObjects:@[@2] identifier:@"id2"]]; - IGListToObjectBlock to = ^NSArray *{ - // move section, and also update the item for "id2" - return @[[IGSectionObject sectionWithObjects:@[@22] identifier:@"id2"], - [IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"]]; - }; - self.dataSource.sections = from; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -799,23 +801,28 @@ deleteIndexPaths:@[] updateIndexPaths:@[] moveIndexPaths:@[]]; + NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1, @2, @3] identifier:@"id1"], + [IGSectionObject sectionWithObjects:@[@2] identifier:@"id2"]]; + // move section, and also update the item for "id2" + NSArray *to = @[[IGSectionObject sectionWithObjects:@[@22] identifier:@"id2"], + [IGSectionObject sectionWithObjects:@[@1, @2, @3] identifier:@"id1"]]; + self.dataSource.sections = from; + id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; self.updater.delegate = mockDelegate; [mockDelegate setExpectationOrderMatters:YES]; - [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; + [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView fromObjects:from toObjects:to listIndexSetResult:[OCMArg checkWithBlock:^BOOL(IGListIndexSetResult *result) { + if (result.inserts.count != 0 || result.deletes.count != 0) { + return NO; + } + // Make sure we note that index 1 is updated (id2 from @[@2] -> @[@22], "id1" moved from section 0 -> 1, and "id2" moved from section 1 -> 0 + return result.updates.firstIndex == 1 && result.moves.count == 2 && [result.moves containsObject:[[IGListMoveIndex alloc] initWithFrom:0 to:1]] && [result.moves containsObject:[[IGListMoveIndex alloc] initWithFrom:1 to:0]]; + }]]; [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:expectedBatchUpdateData collectionView:self.collectionView]; XCTestExpectation *expectation = genExpectation; - NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1, @2, @3] identifier:@"id1"], - [IGSectionObject sectionWithObjects:@[@2] identifier:@"id2"]]; - IGListToObjectBlock to = ^NSArray *{ - // move section, and also update the item for "id2" - return @[[IGSectionObject sectionWithObjects:@[@22] identifier:@"id2"], - [IGSectionObject sectionWithObjects:@[@1, @2, @3] identifier:@"id1"]]; - }; - self.dataSource.sections = from; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -833,25 +840,29 @@ deleteIndexPaths:@[] updateIndexPaths:@[] moveIndexPaths:@[]]; - id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; - self.updater.delegate = mockDelegate; - [mockDelegate setExpectationOrderMatters:YES]; - [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; - [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:expectedBatchUpdateData collectionView:self.collectionView]; - - XCTestExpectation *expectation = genExpectation; NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"], [IGSectionObject sectionWithObjects:@[@2] identifier:@"id2"], [IGSectionObject sectionWithObjects:@[@3] identifier:@"id3"]]; - IGListToObjectBlock to = ^NSArray *{ - // move section, and also update the items for "id2" - return @[[IGSectionObject sectionWithObjects:@[@22, @23] identifier:@"id2"], - [IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"], - [IGSectionObject sectionWithObjects:@[@3] identifier:@"id3"]]; - }; + // move section, and also update the items for "id2" + NSArray *to = @[[IGSectionObject sectionWithObjects:@[@22, @23] identifier:@"id2"], + [IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"], + [IGSectionObject sectionWithObjects:@[@3] identifier:@"id3"]]; self.dataSource.sections = from; + id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; + self.updater.delegate = mockDelegate; + [mockDelegate setExpectationOrderMatters:YES]; + [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView fromObjects:from toObjects:to listIndexSetResult:[OCMArg checkWithBlock:^BOOL(IGListIndexSetResult *result) { + if (result.inserts.count != 0 || result.deletes.count != 0) { + return NO; + } + // Make sure we note that index 1 is updated (id2 from @[@2] -> @[@22], "id1" moved from section 0 -> 1, "id2" moved from section 1 -> 0 + return result.updates.firstIndex == 1 && result.moves.count == 2 && [result.moves containsObject:[[IGListMoveIndex alloc] initWithFrom:0 to:1]] && [result.moves containsObject:[[IGListMoveIndex alloc] initWithFrom:1 to:0]]; + }]]; + [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:expectedBatchUpdateData collectionView:self.collectionView]; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + XCTestExpectation *expectation = genExpectation; + + [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation; @@ -868,22 +879,25 @@ deleteIndexPaths:@[] updateIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]] moveIndexPaths:@[]]; + NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"]]; + NSArray *to = @[[IGSectionObject sectionWithObjects:@[@2] identifier:@"id1"], + [IGSectionObject sectionWithObjects:@[@22] identifier:@"id2"]]; + self.dataSource.sections = from; id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; self.updater.delegate = mockDelegate; [mockDelegate setExpectationOrderMatters:YES]; - [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; + [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView fromObjects:from toObjects:to listIndexSetResult:[OCMArg checkWithBlock:^BOOL(IGListIndexSetResult *result) { + if (result.deletes.count != 0 || result.moves.count != 0) { + return NO; + } + // Make sure we note that index 1 is updated (id1 from @[@1] -> @[@2]), and "id2" was inserted at index 1 + return result.updates.firstIndex == 0 && result.inserts.firstIndex == 1; + }]]; [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:expectedBatchUpdateData collectionView:self.collectionView]; XCTestExpectation *expectation = genExpectation; - NSArray *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"]]; - IGListToObjectBlock to = ^NSArray *{ - // insert new section id2, and also update for section id1 - return @[[IGSectionObject sectionWithObjects:@[@2] identifier:@"id1"], - [IGSectionObject sectionWithObjects:@[@22] identifier:@"id2"]]; - }; - self.dataSource.sections = from; - [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:to animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:genToBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { [expectation fulfill]; }]; waitExpectation;