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
This commit is contained in:
Ryan Nystrom 2018-04-26 08:05:21 -07:00 committed by Facebook Github Bot
parent c1894629d7
commit 3059c5e6f5
9 changed files with 142 additions and 89 deletions

View file

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

View file

@ -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,
};
/**

View file

@ -324,14 +324,28 @@
}
NSArray *fromObjects = self.sectionMap.objects;
NSArray *newObjects = [dataSource objectsForListAdapter:self];
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];
__weak __typeof__(self) weakSelf = self;
[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

View file

@ -41,7 +41,7 @@
return self.hasQueuedReloadData
|| [self.batchUpdates hasChanges]
|| self.fromObjects != nil
|| self.toObjects != nil;
|| self.toObjectsBlock != nil;
}
- (void)performReloadDataWithCollectionView:(UICollectionView *)collectionView {
@ -106,12 +106,25 @@
// create local variables so we can immediately clean our state but pass these items into the batch update block
id<IGListAdapterUpdaterDelegate> 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 convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
// destroy to/from transition items
self.fromObjects = nil;
self.toObjects = nil;
self.toObjectsBlock = nil;
// destroy reloadData state
self.reloadUpdates = nil;
@ -408,11 +421,11 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
}
- (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 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
// 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;

View file

@ -19,11 +19,14 @@
- (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);

View file

@ -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<id <IGListDiffable>> *)fromObjects
toObjects:(nullable NSArray<id <IGListDiffable>> *)toObjects
toObjectsBlock:(nullable IGListToObjectBlock)toObjectsBlock
animated:(BOOL)animated
objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock
completion:(nullable IGListUpdatingCompletion)completion;

View file

@ -61,9 +61,9 @@ static NSMutableArray *linesFromObjects(NSArray *objects) {
[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) {

View file

@ -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<IGListUpdatingCompletion> *completionBlocks;

View file

@ -58,27 +58,27 @@
}
- (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 @@
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 @@
[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 @@
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 @@
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 @@
[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 @@
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 @@
[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 @@
[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 @@
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 @@
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 @@
};
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 @@
[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_whenItemsMoveAndUpdate_thatCollectionViewWorks {
NSArray<IGSectionObject *> *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 @@
[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 @@
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 @@
[[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 @@
[[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 @@
__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 @@
}
@end