diff --git a/Source/IGListDiffKit/IGListExperiments.h b/Source/IGListDiffKit/IGListExperiments.h index d8a288df6..6b75ea151 100644 --- a/Source/IGListDiffKit/IGListExperiments.h +++ b/Source/IGListDiffKit/IGListExperiments.h @@ -50,6 +50,8 @@ typedef struct IGListAdaptiveCoalescingExperimentConfig { NSTimeInterval intervalIncrement; /// This is the maximum coalesce interval, so the slowest and update can wait. NSTimeInterval maxInterval; + /// Coalece using `maxInterval` if view is not visible according to `IGListViewVisibilityTracker` + BOOL useMaxIntervalWhenViewNotVisible; } IGListAdaptiveCoalescingExperimentConfig; /** diff --git a/Source/IGListKit/IGListAdapterUpdater.m b/Source/IGListKit/IGListAdapterUpdater.m index 48cba1326..421569edb 100644 --- a/Source/IGListKit/IGListAdapterUpdater.m +++ b/Source/IGListKit/IGListAdapterUpdater.m @@ -60,7 +60,7 @@ - (void)_queueUpdateIfNeeded { } // Will call `-performUpdateWithCoalescer` - [self.coalescer queueUpdate]; + [self.coalescer queueUpdateForView:self.transactionBuilder.collectionView]; } - (void)performUpdateWithCoalescer:(IGListUpdateCoalescer *)coalescer { diff --git a/Source/IGListKit/Internal/IGListUpdateCoalescer.h b/Source/IGListKit/Internal/IGListUpdateCoalescer.h index 983fb5e2b..5a552e439 100644 --- a/Source/IGListKit/Internal/IGListUpdateCoalescer.h +++ b/Source/IGListKit/Internal/IGListUpdateCoalescer.h @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -#import +#import #if __has_include() #import @@ -34,8 +34,12 @@ NS_SWIFT_NAME(ListUpdateCoalescer) @property (nonatomic, weak) id delegate; -/// Start coalescing updates, which will eventually call `-performUpdateWithCoalescer` -- (void)queueUpdate; +/** + Start coalescing updates, which will eventually call `-performUpdateWithCoalescer` + + @params view View used to track visibility (if enabled in config) + */ +- (void)queueUpdateForView:(nullable UIView *)view; @end diff --git a/Source/IGListKit/Internal/IGListUpdateCoalescer.m b/Source/IGListKit/Internal/IGListUpdateCoalescer.m index ca0d8cf49..af8b894eb 100644 --- a/Source/IGListKit/Internal/IGListUpdateCoalescer.m +++ b/Source/IGListKit/Internal/IGListUpdateCoalescer.m @@ -7,6 +7,8 @@ #import "IGListUpdateCoalescer.h" +#import "IGListViewVisibilityTracker.h" + @implementation IGListUpdateCoalescer { BOOL _hasQueuedUpdate; @@ -15,7 +17,7 @@ @implementation IGListUpdateCoalescer { NSTimeInterval _coalescenceInterval; } -- (void)queueUpdate { +- (void)queueUpdateForView:(nullable UIView *)view { if (_hasQueuedUpdate) { return; } @@ -26,7 +28,7 @@ - (void)queueUpdate { // details on how coalescence is done. if (self.adaptiveCoalescingExperimentConfig.enabled) { - [self _adaptiveDispatchUpdate]; + [self _adaptiveDispatchUpdateForView:view]; } else { [self _regularDispatchUpdate]; } @@ -40,26 +42,41 @@ - (void)_regularDispatchUpdate { }); } -- (void)_adaptiveDispatchUpdate { +static BOOL _isViewVisible(UIView *_Nullable view, IGListAdaptiveCoalescingExperimentConfig config) { + if (config.useMaxIntervalWhenViewNotVisible) { + IGListViewVisibilityTracker *const tracker = IGListViewVisibilityTrackerAttachedOnView((UIView *)view); + if (tracker && tracker.state == IGListViewVisibilityStateNotVisible) { + return NO; + } + } + + return YES; +} + +- (void)_adaptiveDispatchUpdateForView:(nullable UIView *)view { const IGListAdaptiveCoalescingExperimentConfig config = _adaptiveCoalescingExperimentConfig; - const NSTimeInterval timeSinceLastUpdate = -[_lastUpdateStartDate timeIntervalSinceNow]; - if (!_lastUpdateStartDate || timeSinceLastUpdate > _coalescenceInterval) { - // It's been long enough, so lets reset interval and perform update right away - _coalescenceInterval = config.minInterval; - [self _performUpdate]; - return; + const BOOL isViewVisible = _isViewVisible(view, config); + + if (isViewVisible) { + if (!_lastUpdateStartDate || timeSinceLastUpdate > _coalescenceInterval) { + // It's been long enough, so lets reset interval and perform update right away + _coalescenceInterval = config.minInterval; + [self _performUpdate]; + return; + } else { + // If we keep hitting the delay, lets increase it. + _coalescenceInterval = MIN(_coalescenceInterval + config.intervalIncrement, config.maxInterval); + } } - - // If we keep hitting the delay, lets increase it. - _coalescenceInterval = MIN(_coalescenceInterval + config.intervalIncrement, config.maxInterval); - + // Delay by the time remaining in the interval - const NSTimeInterval remainingTime = MAX(_coalescenceInterval - timeSinceLastUpdate, 0); + const NSTimeInterval remainingTime = isViewVisible ? (_coalescenceInterval - timeSinceLastUpdate) : config.maxInterval; + const NSTimeInterval remainingTimeCapped = MAX(remainingTime, 0); _hasQueuedUpdate = YES; __weak __typeof__(self) weakSelf = self; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(remainingTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(remainingTimeCapped * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakSelf _performUpdate]; }); } diff --git a/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.h b/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.h index 83b53ebff..e64af3040 100644 --- a/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.h +++ b/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.h @@ -92,6 +92,8 @@ Change the `UICollectionView` dataSource - (BOOL)hasChanges; +- (nullable UICollectionView *)collectionView; + @end NS_ASSUME_NONNULL_END diff --git a/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.m b/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.m index 1fdc2e197..f11289b4e 100644 --- a/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.m +++ b/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.m @@ -188,4 +188,8 @@ - (BOOL)hasChanges { || self.sectionDataBlock != nil; } +- (nullable UICollectionView *)collectionView { + return self.collectionViewBlock ? self.collectionViewBlock() : nil; +} + @end