mirror of
https://github.com/Instagram/IGListKit
synced 2026-05-23 17:28:22 +00:00
Track batch updates in object and explicit state
Summary: Batch updates are complicated b/c its unknown when the update block will actually execute. When executing the block, we want to collect inserts/deletes/reloads/moves (item and section). Allow mutations to happen synchronously outside of the update block. Tracking state will also help with the auto-diff where we need to allow re-entrant updates. Peeling off a chunk from #494 Issue fixed: #288 - [x] All tests pass. Demo project builds and runs. - [x] I added tests, an experiment, or detailed why my change isn't tested. - [x] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes. Closes https://github.com/Instagram/IGListKit/pull/525 Reviewed By: jessesquires Differential Revision: D4656463 Pulled By: rnystrom fbshipit-source-id: 8f4d3dc21b03d595e02ee9ee9664277e8ead0531
This commit is contained in:
parent
03c316a4db
commit
3f9dea21e0
9 changed files with 170 additions and 59 deletions
|
|
@ -58,6 +58,8 @@ This release closes the [3.0.0 milestone](https://github.com/Instagram/IGListKit
|
|||
|
||||
- Fix a crash when reusing collection views between embedded `IGListAdapter`s. [Ryan Nystrom](https://github.com/rnystrom) [(#517)](https://github.com/Instagram/IGListKit/pull/517)
|
||||
|
||||
- Only collect batch updates when explicitly inside the batch update block, execute them otherwise. Fixes dropped updates. Ryan Nystrom](https://github.com/rnystrom) [(#494)](https://github.com/Instagram/IGListKit/pull/494)
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
|
|
|
|||
|
|
@ -168,6 +168,14 @@
|
|||
2914BEE91DCD15F400C96401 /* IGTestNibSupplementaryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2904861C1DCD02140007F41D /* IGTestNibSupplementaryView.xib */; };
|
||||
2914BEEA1DCD15F400C96401 /* IGTestNibSupplementaryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2904861C1DCD02140007F41D /* IGTestNibSupplementaryView.xib */; };
|
||||
294AC6321DDE4C19002FCE5D /* IGListDiffResultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 294AC6311DDE4C19002FCE5D /* IGListDiffResultTests.m */; };
|
||||
297278BD1E6B58560099D8EA /* IGListBatchUpdates.h in Headers */ = {isa = PBXBuildFile; fileRef = 297278BB1E6B58560099D8EA /* IGListBatchUpdates.h */; };
|
||||
297278BE1E6B58560099D8EA /* IGListBatchUpdates.h in Headers */ = {isa = PBXBuildFile; fileRef = 297278BB1E6B58560099D8EA /* IGListBatchUpdates.h */; };
|
||||
297278BF1E6B58560099D8EA /* IGListBatchUpdates.m in Sources */ = {isa = PBXBuildFile; fileRef = 297278BC1E6B58560099D8EA /* IGListBatchUpdates.m */; };
|
||||
297278C01E6B58560099D8EA /* IGListBatchUpdates.m in Sources */ = {isa = PBXBuildFile; fileRef = 297278BC1E6B58560099D8EA /* IGListBatchUpdates.m */; };
|
||||
297278C11E6B58560099D8EA /* IGListBatchUpdates.m in Sources */ = {isa = PBXBuildFile; fileRef = 297278BC1E6B58560099D8EA /* IGListBatchUpdates.m */; };
|
||||
297278C21E6B58560099D8EA /* IGListBatchUpdates.m in Sources */ = {isa = PBXBuildFile; fileRef = 297278BC1E6B58560099D8EA /* IGListBatchUpdates.m */; };
|
||||
297278C41E6B59D50099D8EA /* IGListBatchUpdateState.h in Headers */ = {isa = PBXBuildFile; fileRef = 297278C31E6B59D50099D8EA /* IGListBatchUpdateState.h */; };
|
||||
297278C51E6B59D50099D8EA /* IGListBatchUpdateState.h in Headers */ = {isa = PBXBuildFile; fileRef = 297278C31E6B59D50099D8EA /* IGListBatchUpdateState.h */; };
|
||||
298DDA1F1E3B0DC800F76F50 /* IGListCollectionViewLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 298DDA1D1E3B0DC800F76F50 /* IGListCollectionViewLayout.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
298DDA201E3B0DC800F76F50 /* IGListCollectionViewLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 298DDA1D1E3B0DC800F76F50 /* IGListCollectionViewLayout.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
298DDA211E3B0DC800F76F50 /* IGListCollectionViewLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 298DDA1E1E3B0DC800F76F50 /* IGListCollectionViewLayout.mm */; };
|
||||
|
|
@ -373,6 +381,9 @@
|
|||
2904861F1DCD02750007F41D /* IGTestNibSupplementaryView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestNibSupplementaryView.m; sourceTree = "<group>"; };
|
||||
294369B01DB1B7AE0025F6E7 /* IGTestNibCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IGTestNibCell.xib; sourceTree = "<group>"; };
|
||||
294AC6311DDE4C19002FCE5D /* IGListDiffResultTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListDiffResultTests.m; sourceTree = "<group>"; };
|
||||
297278BB1E6B58560099D8EA /* IGListBatchUpdates.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListBatchUpdates.h; sourceTree = "<group>"; };
|
||||
297278BC1E6B58560099D8EA /* IGListBatchUpdates.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListBatchUpdates.m; sourceTree = "<group>"; };
|
||||
297278C31E6B59D50099D8EA /* IGListBatchUpdateState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListBatchUpdateState.h; sourceTree = "<group>"; };
|
||||
298DDA1D1E3B0DC800F76F50 /* IGListCollectionViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListCollectionViewLayout.h; sourceTree = "<group>"; };
|
||||
298DDA1E1E3B0DC800F76F50 /* IGListCollectionViewLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = IGListCollectionViewLayout.mm; sourceTree = "<group>"; };
|
||||
298DDA231E3B15EE00F76F50 /* IGListCollectionViewLayoutTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListCollectionViewLayoutTests.m; sourceTree = "<group>"; };
|
||||
|
|
@ -591,6 +602,8 @@
|
|||
0B3B92B71E08D7F5008390ED /* IGListAdapterProxy.h */,
|
||||
0B3B92B81E08D7F5008390ED /* IGListAdapterProxy.m */,
|
||||
0B3B92B91E08D7F5008390ED /* IGListAdapterUpdaterInternal.h */,
|
||||
297278BB1E6B58560099D8EA /* IGListBatchUpdates.h */,
|
||||
297278BC1E6B58560099D8EA /* IGListBatchUpdates.m */,
|
||||
0B3B92BA1E08D7F5008390ED /* IGListDisplayHandler.h */,
|
||||
0B3B92BB1E08D7F5008390ED /* IGListDisplayHandler.m */,
|
||||
0B3B92BC1E08D7F5008390ED /* IGListSectionControllerInternal.h */,
|
||||
|
|
@ -601,6 +614,7 @@
|
|||
0B3B92C11E08D7F5008390ED /* IGListWorkingRangeHandler.mm */,
|
||||
0B3B92C21E08D7F5008390ED /* UICollectionView+IGListBatchUpdateData.h */,
|
||||
0B3B92C31E08D7F5008390ED /* UICollectionView+IGListBatchUpdateData.m */,
|
||||
297278C31E6B59D50099D8EA /* IGListBatchUpdateState.h */,
|
||||
);
|
||||
path = Internal;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -754,6 +768,7 @@
|
|||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
297278C51E6B59D50099D8EA /* IGListBatchUpdateState.h in Headers */,
|
||||
989317641E0ED45900DB93B3 /* IGListCompatibility.h in Headers */,
|
||||
0B3B92FB1E08D7F5008390ED /* IGListAdapterDataSource.h in Headers */,
|
||||
0B3B92E91E08D7F5008390ED /* IGListIndexSetResultInternal.h in Headers */,
|
||||
|
|
@ -794,6 +809,7 @@
|
|||
0B3B92F71E08D7F5008390ED /* IGListAdapter.h in Headers */,
|
||||
0B3B931B1E08D7F5008390ED /* IGListSectionType.h in Headers */,
|
||||
0B3B92E31E08D7F5008390ED /* IGListMoveIndexPath.h in Headers */,
|
||||
297278BE1E6B58560099D8EA /* IGListBatchUpdates.h in Headers */,
|
||||
0B3B93111E08D7F5008390ED /* IGListReloadDataUpdater.h in Headers */,
|
||||
298DDA201E3B0DC800F76F50 /* IGListCollectionViewLayout.h in Headers */,
|
||||
0B3B92FF1E08D7F5008390ED /* IGListAdapterUpdater.h in Headers */,
|
||||
|
|
@ -807,6 +823,7 @@
|
|||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
297278C41E6B59D50099D8EA /* IGListBatchUpdateState.h in Headers */,
|
||||
989317631E0ED45900DB93B3 /* IGListCompatibility.h in Headers */,
|
||||
0B3B92FA1E08D7F5008390ED /* IGListAdapterDataSource.h in Headers */,
|
||||
0B3B92E81E08D7F5008390ED /* IGListIndexSetResultInternal.h in Headers */,
|
||||
|
|
@ -847,6 +864,7 @@
|
|||
0B3B92F61E08D7F5008390ED /* IGListAdapter.h in Headers */,
|
||||
0B3B931A1E08D7F5008390ED /* IGListSectionType.h in Headers */,
|
||||
0B3B92E21E08D7F5008390ED /* IGListMoveIndexPath.h in Headers */,
|
||||
297278BD1E6B58560099D8EA /* IGListBatchUpdates.h in Headers */,
|
||||
0B3B93101E08D7F5008390ED /* IGListReloadDataUpdater.h in Headers */,
|
||||
298DDA1F1E3B0DC800F76F50 /* IGListCollectionViewLayout.h in Headers */,
|
||||
0B3B92FE1E08D7F5008390ED /* IGListAdapterUpdater.h in Headers */,
|
||||
|
|
@ -1215,6 +1233,7 @@
|
|||
298DDA221E3B0DC800F76F50 /* IGListCollectionViewLayout.mm in Sources */,
|
||||
0B3B93311E08D7F5008390ED /* IGListAdapterProxy.m in Sources */,
|
||||
0B3B92CD1E08D7F5008390ED /* IGListDiff.mm in Sources */,
|
||||
297278C11E6B58560099D8EA /* IGListBatchUpdates.m in Sources */,
|
||||
0B3B931F1E08D7F5008390ED /* IGListSingleSectionController.m in Sources */,
|
||||
0B3B92D51E08D7F5008390ED /* IGListIndexPathResult.m in Sources */,
|
||||
0B3B93371E08D7F5008390ED /* IGListDisplayHandler.m in Sources */,
|
||||
|
|
@ -1237,6 +1256,7 @@
|
|||
298DDA381E3B168E00F76F50 /* IGLayoutTestItem.m in Sources */,
|
||||
885FE2361DC51B76009CE2B4 /* IGListStackSectionControllerTests.m in Sources */,
|
||||
885FE2311DC51B76009CE2B4 /* IGListDisplayHandlerTests.m in Sources */,
|
||||
297278C21E6B58560099D8EA /* IGListBatchUpdates.m in Sources */,
|
||||
0B40C5F41E01CBCB00378109 /* IGListCollectionViewTests.m in Sources */,
|
||||
298DDA3B1E3B16F800F76F50 /* IGLayoutTestDataSource.m in Sources */,
|
||||
29C474901DDF460500AE68CE /* IGListSectionMapTests.m in Sources */,
|
||||
|
|
@ -1294,6 +1314,7 @@
|
|||
298DDA211E3B0DC800F76F50 /* IGListCollectionViewLayout.mm in Sources */,
|
||||
0B3B93301E08D7F5008390ED /* IGListAdapterProxy.m in Sources */,
|
||||
0B3B92CC1E08D7F5008390ED /* IGListDiff.mm in Sources */,
|
||||
297278BF1E6B58560099D8EA /* IGListBatchUpdates.m in Sources */,
|
||||
0B3B931E1E08D7F5008390ED /* IGListSingleSectionController.m in Sources */,
|
||||
0B3B92D41E08D7F5008390ED /* IGListIndexPathResult.m in Sources */,
|
||||
0B3B93361E08D7F5008390ED /* IGListDisplayHandler.m in Sources */,
|
||||
|
|
@ -1316,6 +1337,7 @@
|
|||
298DDA391E3B168F00F76F50 /* IGLayoutTestItem.m in Sources */,
|
||||
88144F1C1D870EDC007C7F66 /* IGTestStackedDataSource.m in Sources */,
|
||||
88144F181D870EDC007C7F66 /* IGTestDelegateController.m in Sources */,
|
||||
297278C01E6B58560099D8EA /* IGListBatchUpdates.m in Sources */,
|
||||
1F0A68C51DF8D5B9009E8ADE /* IGListCollectionViewTests.m in Sources */,
|
||||
298DDA3A1E3B16F600F76F50 /* IGLayoutTestDataSource.m in Sources */,
|
||||
88144F0D1D870EDC007C7F66 /* IGListDisplayHandlerTests.m in Sources */,
|
||||
|
|
|
|||
|
|
@ -29,12 +29,7 @@
|
|||
_completionBlocks = [NSMutableArray new];
|
||||
_itemUpdateBlocks = [NSMutableArray new];
|
||||
|
||||
_reloadSections = [NSMutableIndexSet new];
|
||||
|
||||
_deleteIndexPaths = [NSMutableSet new];
|
||||
_insertIndexPaths = [NSMutableSet new];
|
||||
_moveIndexPaths = [NSMutableSet new];
|
||||
_reloadIndexPaths = [NSMutableSet new];
|
||||
_batchUpdatesCollector = [IGListBatchUpdates new];
|
||||
|
||||
_allowsBackgroundReloading = YES;
|
||||
}
|
||||
|
|
@ -65,7 +60,7 @@
|
|||
NSArray *itemUpdateBlocks = [self.itemUpdateBlocks copy];
|
||||
|
||||
// item updates must not send mutations to the collection view while we are reloading
|
||||
self.batchUpdateOrReloadInProgress = YES;
|
||||
self.state = IGListBatchUpdateStateExecutingBatchUpdateBlock;
|
||||
|
||||
if (reloadUpdates) {
|
||||
reloadUpdates();
|
||||
|
|
@ -77,6 +72,8 @@
|
|||
for (IGListItemUpdateBlock itemUpdateBlock in itemUpdateBlocks) {
|
||||
itemUpdateBlock();
|
||||
}
|
||||
|
||||
self.state = IGListBatchUpdateStateExecutedBatchUpdateBlock;
|
||||
|
||||
// cleanup state before reloading and calling completion blocks
|
||||
[self cleanupState];
|
||||
|
|
@ -88,11 +85,11 @@
|
|||
[collectionView layoutIfNeeded];
|
||||
[delegate listAdapterUpdater:self didReloadDataWithCollectionView:collectionView];
|
||||
|
||||
self.batchUpdateOrReloadInProgress = NO;
|
||||
|
||||
for (IGListUpdatingCompletion block in completionBlocks) {
|
||||
block(YES);
|
||||
}
|
||||
|
||||
self.state = IGListBatchUpdateStateIdle;
|
||||
}
|
||||
|
||||
static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable>> *objects) {
|
||||
|
|
@ -112,7 +109,7 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable
|
|||
|
||||
- (void)performBatchUpdatesWithCollectionView:(UICollectionView *)collectionView {
|
||||
IGAssertMainThread();
|
||||
IGAssert(!self.batchUpdateOrReloadInProgress, @"should not call this when updating");
|
||||
IGAssert(self.state == IGListBatchUpdateStateIdle, @"Should not call batch updates when state isn't idle");
|
||||
|
||||
// bail early if the collection view has been deallocated in the time since the update was queued
|
||||
if (collectionView == nil) {
|
||||
|
|
@ -132,6 +129,8 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable
|
|||
[self cleanupState];
|
||||
|
||||
void (^executeUpdateBlocks)() = ^{
|
||||
self.state = IGListBatchUpdateStateExecutingBatchUpdateBlock;
|
||||
|
||||
// run the update block so that the adapter can set its items. this makes sure that just before the update is
|
||||
// committed that the data source is updated to the /latest/ "toObjects". this makes the data source in sync
|
||||
// with the items that the updater is transitioning to
|
||||
|
|
@ -145,9 +144,13 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable
|
|||
for (IGListItemUpdateBlock itemUpdateBlock in itemUpdateBlocks) {
|
||||
itemUpdateBlock();
|
||||
}
|
||||
|
||||
self.state = IGListBatchUpdateStateExecutedBatchUpdateBlock;
|
||||
};
|
||||
|
||||
void (^executeCompletionBlocks)(BOOL) = ^(BOOL finished) {
|
||||
self.state = IGListBatchUpdateStateIdle;
|
||||
|
||||
for (IGListUpdatingCompletion block in completionBlocks) {
|
||||
block(finished);
|
||||
}
|
||||
|
|
@ -157,12 +160,11 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable
|
|||
// reload data, execute completion blocks, and get outta here
|
||||
const BOOL iOS83OrLater = (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_8_3);
|
||||
if (iOS83OrLater && self.allowsBackgroundReloading && collectionView.window == nil) {
|
||||
[self beginPerformBatchUpdatestoObjects:toObjects];
|
||||
[self beginPerformBatchUpdatesToObjects:toObjects];
|
||||
executeUpdateBlocks();
|
||||
[self cleanupUpdateBlockState];
|
||||
[self performBatchUpdatesItemBlockApplied];
|
||||
[collectionView reloadData];
|
||||
[self endPerformBatchUpdates];
|
||||
executeCompletionBlocks(YES);
|
||||
return;
|
||||
}
|
||||
|
|
@ -183,11 +185,7 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable
|
|||
|
||||
updateData = [self flushCollectionView:collectionView
|
||||
withDiffResult:result
|
||||
reloadSections:[self.reloadSections copy]
|
||||
deleteIndexPaths:[self.deleteIndexPaths copy]
|
||||
insertIndexPaths:[self.insertIndexPaths copy]
|
||||
moveIndexPaths:[self.moveIndexPaths copy]
|
||||
reloadIndexPaths:[self.reloadIndexPaths copy]
|
||||
batchUpdatesCollector:self.batchUpdatesCollector
|
||||
fromObjects:fromObjects];
|
||||
|
||||
[self cleanupUpdateBlockState];
|
||||
|
|
@ -195,8 +193,6 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable
|
|||
};
|
||||
|
||||
void (^completionBlock)(BOOL) = ^(BOOL finished) {
|
||||
[self endPerformBatchUpdates];
|
||||
|
||||
executeCompletionBlocks(finished);
|
||||
|
||||
[delegate listAdapterUpdater:self didPerformBatchUpdates:updateData collectionView:collectionView];
|
||||
|
|
@ -207,7 +203,7 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable
|
|||
};
|
||||
|
||||
// disables multiple performBatchUpdates: from happening at the same time
|
||||
[self beginPerformBatchUpdatestoObjects:toObjects];
|
||||
[self beginPerformBatchUpdatesToObjects:toObjects];
|
||||
|
||||
@try {
|
||||
[delegate listAdapterUpdater:self willPerformBatchUpdatesWithCollectionView:collectionView];
|
||||
|
|
@ -255,17 +251,13 @@ void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
|||
|
||||
- (IGListBatchUpdateData *)flushCollectionView:(UICollectionView *)collectionView
|
||||
withDiffResult:(IGListIndexSetResult *)diffResult
|
||||
reloadSections:(NSIndexSet *)reloadSections
|
||||
deleteIndexPaths:(NSSet<NSIndexPath *> *)deleteIndexPaths
|
||||
insertIndexPaths:(NSSet<NSIndexPath *> *)insertIndexPaths
|
||||
moveIndexPaths:(NSSet<IGListMoveIndexPath *> *)moveFromIndexPaths
|
||||
reloadIndexPaths:(NSSet<NSIndexPath *> *)reloadIndexPaths
|
||||
batchUpdatesCollector:(IGListBatchUpdates *)batchUpdatesCollector
|
||||
fromObjects:(NSArray <id<IGListDiffable>> *)fromObjects {
|
||||
NSSet *moves = [[NSSet alloc] initWithArray:diffResult.moves];
|
||||
|
||||
// combine section reloads from the diff and manual reloads via reloadItems:
|
||||
NSMutableIndexSet *reloads = [diffResult.updates mutableCopy];
|
||||
[reloads addIndexes:reloadSections];
|
||||
[reloads addIndexes:batchUpdatesCollector.sectionReloads];
|
||||
|
||||
NSMutableIndexSet *inserts = [diffResult.inserts mutableCopy];
|
||||
NSMutableIndexSet *deletes = [diffResult.deletes mutableCopy];
|
||||
|
|
@ -284,27 +276,23 @@ void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
|||
IGListBatchUpdateData *updateData = [[IGListBatchUpdateData alloc] initWithInsertSections:inserts
|
||||
deleteSections:deletes
|
||||
moveSections:moves
|
||||
insertIndexPaths:insertIndexPaths
|
||||
deleteIndexPaths:deleteIndexPaths
|
||||
moveIndexPaths:moveFromIndexPaths
|
||||
reloadIndexPaths:reloadIndexPaths];
|
||||
insertIndexPaths:batchUpdatesCollector.itemInserts
|
||||
deleteIndexPaths:batchUpdatesCollector.itemDeletes
|
||||
moveIndexPaths:batchUpdatesCollector.itemMoves
|
||||
reloadIndexPaths:batchUpdatesCollector.itemReloads];
|
||||
[collectionView ig_applyBatchUpdateData:updateData];
|
||||
return updateData;
|
||||
}
|
||||
|
||||
- (void)beginPerformBatchUpdatestoObjects:(NSArray *)toObjects {
|
||||
self.batchUpdateOrReloadInProgress = YES;
|
||||
- (void)beginPerformBatchUpdatesToObjects:(NSArray *)toObjects {
|
||||
self.pendingTransitionToObjects = toObjects;
|
||||
self.state = IGListBatchUpdateStateQueuedBatchUpdate;
|
||||
}
|
||||
|
||||
- (void)performBatchUpdatesItemBlockApplied {
|
||||
self.pendingTransitionToObjects = nil;
|
||||
}
|
||||
|
||||
- (void)endPerformBatchUpdates {
|
||||
self.batchUpdateOrReloadInProgress = NO;
|
||||
}
|
||||
|
||||
- (void)cleanupState {
|
||||
self.queuedUpdateIsAnimated = YES;
|
||||
|
||||
|
|
@ -325,11 +313,7 @@ void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
|||
}
|
||||
|
||||
- (void)cleanupUpdateBlockState {
|
||||
[self.reloadSections removeAllIndexes];
|
||||
[self.deleteIndexPaths removeAllObjects];
|
||||
[self.insertIndexPaths removeAllObjects];
|
||||
[self.moveIndexPaths removeAllObjects];
|
||||
[self.reloadIndexPaths removeAllObjects];
|
||||
self.batchUpdatesCollector = [IGListBatchUpdates new];
|
||||
}
|
||||
|
||||
- (void)queueUpdateWithCollectionView:(UICollectionView *)collectionView {
|
||||
|
|
@ -342,7 +326,8 @@ void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
|||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (weakSelf.batchUpdateOrReloadInProgress || ![weakSelf hasChanges]) {
|
||||
if (weakSelf.state != IGListBatchUpdateStateIdle
|
||||
|| ![weakSelf hasChanges]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -441,8 +426,8 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
IGAssertMainThread();
|
||||
IGParameterAssert(collectionView != nil);
|
||||
IGParameterAssert(indexPaths != nil);
|
||||
if (self.batchUpdateOrReloadInProgress) {
|
||||
[self.insertIndexPaths addObjectsFromArray:indexPaths];
|
||||
if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) {
|
||||
[self.batchUpdatesCollector.itemInserts addObjectsFromArray:indexPaths];
|
||||
} else {
|
||||
[self.delegate listAdapterUpdater:self willInsertIndexPaths:indexPaths collectionView:collectionView];
|
||||
[collectionView insertItemsAtIndexPaths:indexPaths];
|
||||
|
|
@ -453,8 +438,8 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
IGAssertMainThread();
|
||||
IGParameterAssert(collectionView != nil);
|
||||
IGParameterAssert(indexPaths != nil);
|
||||
if (self.batchUpdateOrReloadInProgress) {
|
||||
[self.deleteIndexPaths addObjectsFromArray:indexPaths];
|
||||
if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) {
|
||||
[self.batchUpdatesCollector.itemDeletes addObjectsFromArray:indexPaths];
|
||||
} else {
|
||||
[self.delegate listAdapterUpdater:self willDeleteIndexPaths:indexPaths collectionView:collectionView];
|
||||
[collectionView deleteItemsAtIndexPaths:indexPaths];
|
||||
|
|
@ -464,9 +449,9 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
- (void)moveItemInCollectionView:(UICollectionView *)collectionView
|
||||
fromIndexPath:(NSIndexPath *)fromIndexPath
|
||||
toIndexPath:(NSIndexPath *)toIndexPath {
|
||||
if (self.batchUpdateOrReloadInProgress) {
|
||||
if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) {
|
||||
IGListMoveIndexPath *move = [[IGListMoveIndexPath alloc] initWithFrom:fromIndexPath to:toIndexPath];
|
||||
[self.moveIndexPaths addObject:move];
|
||||
[self.batchUpdatesCollector.itemMoves addObject:move];
|
||||
} else {
|
||||
[self.delegate listAdapterUpdater:self willMoveFromIndexPath:fromIndexPath toIndexPath:toIndexPath collectionView:collectionView];
|
||||
[collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
|
||||
|
|
@ -477,8 +462,8 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
IGAssertMainThread();
|
||||
IGParameterAssert(collectionView != nil);
|
||||
IGParameterAssert(indexPaths != nil);
|
||||
if (self.batchUpdateOrReloadInProgress) {
|
||||
[self.reloadIndexPaths addObjectsFromArray:indexPaths];
|
||||
if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) {
|
||||
[self.batchUpdatesCollector.itemReloads addObjectsFromArray:indexPaths];
|
||||
} else {
|
||||
[self.delegate listAdapterUpdater:self willReloadIndexPaths:indexPaths collectionView:collectionView];
|
||||
[collectionView reloadItemsAtIndexPaths:indexPaths];
|
||||
|
|
@ -506,8 +491,8 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons
|
|||
IGAssertMainThread();
|
||||
IGParameterAssert(collectionView != nil);
|
||||
IGParameterAssert(sections != nil);
|
||||
if (self.batchUpdateOrReloadInProgress) {
|
||||
[self.reloadSections addIndexes:sections];
|
||||
if (self.state == IGListBatchUpdateStateExecutingBatchUpdateBlock) {
|
||||
[self.batchUpdatesCollector.sectionReloads addIndexes:sections];
|
||||
} else {
|
||||
[self.delegate listAdapterUpdater:self willReloadSections:sections collectionView:collectionView];
|
||||
[collectionView reloadSections:sections];
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
#import <IGListKit/IGListMoveIndexPath.h>
|
||||
|
||||
#import "IGListAdapterUpdater.h"
|
||||
#import "IGListBatchUpdateState.h"
|
||||
#import "IGListBatchUpdates.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
|
@ -32,11 +34,7 @@ FOUNDATION_EXTERN void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
|||
|
||||
@property (nonatomic, assign) BOOL queuedUpdateIsAnimated;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *deleteIndexPaths;
|
||||
@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *insertIndexPaths;
|
||||
@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *reloadIndexPaths;
|
||||
@property (nonatomic, strong, readonly) NSMutableSet<IGListMoveIndexPath *> *moveIndexPaths;
|
||||
@property (nonatomic, strong, readonly) NSMutableIndexSet *reloadSections;
|
||||
@property (nonatomic, strong) IGListBatchUpdates *batchUpdatesCollector;
|
||||
|
||||
@property (nonatomic, copy, nullable) IGListObjectTransitionBlock objectTransitionBlock;
|
||||
@property (nonatomic, copy, nullable) NSMutableArray<IGListItemUpdateBlock> *itemUpdateBlocks;
|
||||
|
|
@ -44,7 +42,7 @@ FOUNDATION_EXTERN void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
|||
@property (nonatomic, copy, nullable) IGListReloadUpdateBlock reloadUpdates;
|
||||
@property (nonatomic, assign, getter=hasQueuedReloadData) BOOL queuedReloadData;
|
||||
|
||||
@property (nonatomic, assign) BOOL batchUpdateOrReloadInProgress;
|
||||
@property (nonatomic, assign) IGListBatchUpdateState state;
|
||||
|
||||
- (void)performReloadDataWithCollectionView:(UICollectionView *)collectionView;
|
||||
- (void)performBatchUpdatesWithCollectionView:(UICollectionView *)collectionView;
|
||||
|
|
|
|||
17
Source/Internal/IGListBatchUpdateState.h
Normal file
17
Source/Internal/IGListBatchUpdateState.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM (NSInteger, IGListBatchUpdateState) {
|
||||
IGListBatchUpdateStateIdle,
|
||||
IGListBatchUpdateStateQueuedBatchUpdate,
|
||||
IGListBatchUpdateStateExecutingBatchUpdateBlock,
|
||||
IGListBatchUpdateStateExecutedBatchUpdateBlock,
|
||||
};
|
||||
25
Source/Internal/IGListBatchUpdates.h
Normal file
25
Source/Internal/IGListBatchUpdates.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <IGListKit/IGListMacros.h>
|
||||
|
||||
@class IGListMoveIndexPath;
|
||||
|
||||
IGLK_SUBCLASSING_RESTRICTED
|
||||
@interface IGListBatchUpdates : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSMutableIndexSet *sectionReloads;
|
||||
@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *itemInserts;
|
||||
@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *itemDeletes;
|
||||
@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *itemReloads;
|
||||
@property (nonatomic, strong, readonly) NSMutableSet<IGListMoveIndexPath *> *itemMoves;
|
||||
|
||||
@end
|
||||
25
Source/Internal/IGListBatchUpdates.m
Normal file
25
Source/Internal/IGListBatchUpdates.m
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Copyright (c) 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "IGListBatchUpdates.h"
|
||||
|
||||
@implementation IGListBatchUpdates
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_sectionReloads = [NSMutableIndexSet new];
|
||||
_itemInserts = [NSMutableSet new];
|
||||
_itemMoves = [NSMutableSet new];
|
||||
_itemReloads = [NSMutableSet new];
|
||||
_itemDeletes = [NSMutableSet new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -1436,4 +1436,41 @@
|
|||
[self waitForExpectationsWithTimeout:15 handler:nil];
|
||||
}
|
||||
|
||||
- (void)test_whenDidUpdateAsyncReloads_withBatchUpdatesInProgress_thatReloadIsExecuted {
|
||||
[self setupWithObjects:@[
|
||||
genTestObject(@1, @1)
|
||||
]];
|
||||
|
||||
IGTestDelegateController *section = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
|
||||
|
||||
XCTestExpectation *expectation1 = genExpectation;
|
||||
__weak __typeof__(section) weakSection = section;
|
||||
section.itemUpdateBlock = ^{
|
||||
// currently inside -[IGListSectionType didUpdateToObject:], change the item (note: NEVER do this) manually
|
||||
// so that the data powering numberOfItems changes (1 to 2). dispatch_async the update to skip outside of the
|
||||
// -[UICollectionView performBatchUpdates:completion:] block execution
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
weakSection.item = genTestObject(@1, @2);
|
||||
[weakSection.collectionContext reloadSectionController:weakSection];
|
||||
[expectation1 fulfill];
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
|
||||
});
|
||||
};
|
||||
|
||||
// add an object so that a batch update is triggered (diff result has changes)
|
||||
self.dataSource.objects = @[
|
||||
genTestObject(@1, @1),
|
||||
genTestObject(@2, @1)
|
||||
];
|
||||
|
||||
XCTestExpectation *expectation2 = genExpectation;
|
||||
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
|
||||
// verify that the section still has 2 items since this completion executes AFTER the reload block above
|
||||
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
|
||||
[expectation2 fulfill];
|
||||
}];
|
||||
|
||||
[self waitForExpectationsWithTimeout:15 handler:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
@interface IGTestDelegateController : IGListSectionController <IGListSectionType, IGListDisplayDelegate, IGListWorkingRangeDelegate>
|
||||
|
||||
@property (nonatomic, strong, readonly) IGTestObject *item;
|
||||
@property (nonatomic, strong) IGTestObject *item;
|
||||
|
||||
@property (nonatomic, assign) CGFloat height;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue