mirror of
https://github.com/Instagram/IGListKit
synced 2026-05-24 01:38:26 +00:00
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:
parent
c1894629d7
commit
3059c5e6f5
9 changed files with 142 additions and 89 deletions
|
|
@ -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
|
||||
-----
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue