Add more info in IGListAdapterUpdater willPerformBatchUpdates call

Summary: Provide additional context in willPerformBatchUpdates, which can be useful for debugging.

Reviewed By: lorixx

Differential Revision: D10436588

fbshipit-source-id: a59803affc24ca20f7726e36117df6126b984afb
This commit is contained in:
Jeremy Cohen 2018-10-21 07:56:39 -07:00 committed by Facebook Github Bot
parent 13d22fbfed
commit b200ddacf5
4 changed files with 110 additions and 80 deletions

View file

@ -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)

View file

@ -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) {

View file

@ -10,6 +10,9 @@
#import <IGListKit/IGListBatchUpdateData.h>
@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 <id<IGListDiffable>> *)fromObjects
toObjects:(nullable NSArray <id<IGListDiffable>> *)toObjects
listIndexSetResult:(nullable IGListIndexSetResult *)listIndexSetResults;
/**
Notifies the delegate that the updater successfully finished `-[UICollectionView performBatchUpdates:completion:]`.

View file

@ -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<IGSectionObject *> *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<IGSectionObject *> *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id"]];
NSArray<IGSectionObject *> *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<IGSectionObject *> *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<IGSectionObject *> *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id"]];
// Update the items
NSArray<IGSectionObject *> *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<IGSectionObject *> *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<IGSectionObject *> *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id"]];
// more items in the section
NSArray<IGSectionObject *> *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<IGSectionObject *> *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<IGSectionObject *> *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"],
[IGSectionObject sectionWithObjects:@[@2] identifier:@"id2"]];
// move section, and also update the item for "id2"
NSArray<IGSectionObject *> *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<IGSectionObject *> *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<IGSectionObject *> *from = @[[IGSectionObject sectionWithObjects:@[@1, @2, @3] identifier:@"id1"],
[IGSectionObject sectionWithObjects:@[@2] identifier:@"id2"]];
// move section, and also update the item for "id2"
NSArray<IGSectionObject *> *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<IGSectionObject *> *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<IGSectionObject *> *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<IGSectionObject *> *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<IGSectionObject *> *from = @[[IGSectionObject sectionWithObjects:@[@1] identifier:@"id1"]];
NSArray<IGSectionObject *> *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<IGSectionObject *> *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;