diff --git a/Source/IGListKit/IGListAdapterUpdater.m b/Source/IGListKit/IGListAdapterUpdater.m index d2c10297..0c5e37a5 100644 --- a/Source/IGListKit/IGListAdapterUpdater.m +++ b/Source/IGListKit/IGListAdapterUpdater.m @@ -17,6 +17,8 @@ #import "IGListReloadIndexPath.h" #import "UICollectionView+IGListBatchUpdateData.h" +typedef void (^IGListAdapterUpdaterDiffResultBlock)(IGListIndexSetResult *); + @implementation IGListAdapterUpdater - (instancetype)init { @@ -112,6 +114,7 @@ void (^objectTransitionBlock)(NSArray *) = [self.objectTransitionBlock copy]; const BOOL animated = self.queuedUpdateIsAnimated; const BOOL allowsReloadingOnTooManyUpdates = self.allowsReloadingOnTooManyUpdates; + const IGListExperiment experiments = self.experiments; IGListBatchUpdates *batchUpdates = self.batchUpdates; // clean up all state so that new updates can be coalesced while the current update is in flight @@ -197,12 +200,6 @@ return; } - const IGListExperiment experiments = self.experiments; - - IGListIndexSetResult *(^performDiff)(void) = ^{ - return IGListDiffExperiment(fromObjects, toObjects, IGListDiffEquality, experiments); - }; - // block executed in the first param block of -[UICollectionView performBatchUpdates:completion:] void (^batchUpdatesBlock)(IGListIndexSetResult *result) = ^(IGListIndexSetResult *result){ executeUpdateBlocks(); @@ -230,7 +227,7 @@ experiments, self.movesAsDeletesInserts, self.preferItemReloadsForSectionReloads); -} + } [self _cleanStateAfterUpdates]; [self _performBatchUpdatesItemBlockApplied]; @@ -306,17 +303,12 @@ willPerformBatchUpdatesWithCollectionView:collectionView } }; - if (IGListExperimentEnabled(experiments, IGListExperimentBackgroundDiffing)) { - dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ - IGListIndexSetResult *result = performDiff(); - dispatch_async(dispatch_get_main_queue(), ^{ - tryToPerformUpdate(result); - }); - }); - } else { - IGListIndexSetResult *result = performDiff(); + const BOOL onBackgroundThread = IGListExperimentEnabled(experiments, IGListExperimentBackgroundDiffing); + [delegate listAdapterUpdater:self willDiffFromObjects:fromObjects toObjects:toObjects]; + IGListAdapterUpdaterPerformDiffing(fromObjects, toObjects, IGListDiffEquality, onBackgroundThread, experiments, ^(IGListIndexSetResult *result){ + [delegate listAdapterUpdater:self didDiffWithResults:result onBackgroundThread:onBackgroundThread]; tryToPerformUpdate(result); - } + }); } - (void)_beginPerformBatchUpdatesToObjects:(NSArray *)toObjects { @@ -374,7 +366,6 @@ willPerformBatchUpdatesWithCollectionView:collectionView }); } - #pragma mark - IGListUpdatingDelegate static BOOL IGListIsEqual(const void *a, const void *b, NSUInteger (*size)(const void *item)) { @@ -575,5 +566,25 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons } } +#pragma mark - Diffing + +static void IGListAdapterUpdaterPerformDiffing(NSArray> *_Nullable oldArray, + NSArray> *_Nullable newArray, + IGListDiffOption option, + IGListExperiment experiments, + BOOL onBackgroundThread, + IGListAdapterUpdaterDiffResultBlock completion) { + if (onBackgroundThread) { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ + IGListIndexSetResult *result = IGListDiffExperiment(oldArray, newArray, option, experiments); + dispatch_async(dispatch_get_main_queue(), ^{ + completion(result); + }); + }); + } else { + IGListIndexSetResult *result = IGListDiffExperiment(oldArray, newArray, option, experiments); + completion(result); + } +} @end diff --git a/Source/IGListKit/IGListAdapterUpdaterDelegate.h b/Source/IGListKit/IGListAdapterUpdaterDelegate.h index 0f9aee84..695875a5 100644 --- a/Source/IGListKit/IGListAdapterUpdaterDelegate.h +++ b/Source/IGListKit/IGListAdapterUpdaterDelegate.h @@ -21,6 +21,28 @@ NS_ASSUME_NONNULL_BEGIN NS_SWIFT_NAME(ListAdapterUpdaterDelegate) @protocol IGListAdapterUpdaterDelegate +/** + Notifies the delegate that the updater is about to beging diffing. + + @param listAdapterUpdater The adapter updater owning the transition. + @param fromObjects The items transitioned from in the batch updates, if any. + @param toObjects The items transitioned to in the batch updates, if any. + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater + willDiffFromObjects:(nullable NSArray > *)fromObjects + toObjects:(nullable NSArray > *)toObjects; + +/** + Notifies the delegate that the updater finished diffing. + + @param listAdapterUpdater The adapter updater owning the transition. + @param listIndexSetResults The diffing result of indices to be inserted/removed/updated/moved/etc. + @param onBackgroundThread Was the diffing performed on a background thread + */ +- (void)listAdapterUpdater:(IGListAdapterUpdater *)listAdapterUpdater + didDiffWithResults:(nullable IGListIndexSetResult *)listIndexSetResults + onBackgroundThread:(BOOL)onBackgroundThread; + /** Notifies the delegate that the updater will call `-[UICollectionView performBatchUpdates:completion:]`. diff --git a/Tests/IGListAdapterUpdaterTests.m b/Tests/IGListAdapterUpdaterTests.m index da5a38d5..7eb14c01 100644 --- a/Tests/IGListAdapterUpdaterTests.m +++ b/Tests/IGListAdapterUpdaterTests.m @@ -814,6 +814,44 @@ [self waitForExpectationsWithTimeout:30 handler:nil]; } +- (void)test_whenPerformingUpdate_thatCallsDiffingDelegate { + self.updater.experiments |= IGListExperimentBackgroundDiffing; + + NSArray *from = @[ + [IGSectionObject sectionWithObjects:@[] identifier:@"0"] + ]; + NSArray *to = @[ + [IGSectionObject sectionWithObjects:@[] identifier:@"0"], + [IGSectionObject sectionWithObjects:@[] identifier:@"1"] + ]; + IGListToObjectBlock toBlock = ^NSArray *{ + return to; + }; + + self.dataSource.sections = from; + [self.updater performReloadDataWithCollectionViewBlock:[self collectionViewBlock]]; + + id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; + self.updater.delegate = mockDelegate; + [mockDelegate setExpectationOrderMatters:YES]; + [[mockDelegate expect] listAdapterUpdater:self.updater willDiffFromObjects:from toObjects:to]; + [[mockDelegate expect] listAdapterUpdater:self.updater didDiffWithResults:[OCMArg checkWithBlock:^BOOL(IGListIndexSetResult *result) { + return [result.inserts isEqualToIndexSet:[NSIndexSet indexSetWithIndex:1]] + && result.deletes.count == 0 + && result.updates.count == 0 + && result.moves.count == 0; + }] onBackgroundThread:YES]; + + XCTestExpectation *expectation = genExpectation; + + [self.updater performUpdateWithCollectionViewBlock:[self collectionViewBlock] fromObjects:from toObjectsBlock:toBlock animated:NO objectTransitionBlock:self.updateBlock completion:^(BOOL finished) { + [expectation fulfill]; + }]; + waitExpectation; + [mockDelegate verify]; +} + + # pragma mark - preferItemReloadsFroSectionReloads - (void)test_whenReloadIsCalledWithSameItemCount_andPreferItemReload_updateIndexPathsHappen {