diff --git a/CHANGELOG.md b/CHANGELOG.md index 007845f..d7177f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ This release closes the [2.1.0 milestone](https://github.com/Instagram/IGListKit - Added CocoaPods subspec for diffing, `IGListKit/Diffing` and an [installation guide](https://instagram.github.io/IGListKit/installation.html). [Sherlouk](https://github.com/Sherlouk) [(#368)](https://github.com/Instagram/IGListKit/pull/368) +- Added `allowsBackgroundReloading` flag (default `YES`) to `IGListAdapterUpdater` so users can configure this behavior as needed. [Adlai-Holler](https://github.com/Adlai-Holler) [(#375)](https://github.com/Instagram/IGListKit/pull/375) + ### Fixes - Avoid `UICollectionView` crashes when queueing a reload and insert/delete on the same item as well as reloading an item in a section that is animating. [Ryan Nystrom](https://github.com/rnystrom) [(#325)](https://github.com/Instagram/IGListKit/pull/325) diff --git a/Source/IGListAdapterUpdater.h b/Source/IGListAdapterUpdater.h index 8ed2a7e..7ae34fa 100644 --- a/Source/IGListAdapterUpdater.h +++ b/Source/IGListAdapterUpdater.h @@ -37,6 +37,17 @@ IGLK_SUBCLASSING_RESTRICTED */ @property (nonatomic, assign) BOOL movesAsDeletesInserts; +/** + A flag indicating whether this updater should skip diffing and simply call + `reloadData` for updates when the collection view is not in a window. The default value is `YES`. + + @note This will result in better performance, but will not generate the same delegate + callbacks. If using a custom layout, it will not receive `prepareForCollectionViewUpdates:`. + + @warning On iOS < 8.3, this behavior is unsupported and will always be treated as `NO`. + */ +@property (nonatomic, assign) BOOL allowsBackgroundReloading; + /** A bitmask of experiments to conduct on the updater. */ diff --git a/Source/IGListAdapterUpdater.m b/Source/IGListAdapterUpdater.m index 0a9521f..4dd5372 100644 --- a/Source/IGListAdapterUpdater.m +++ b/Source/IGListAdapterUpdater.m @@ -16,9 +16,7 @@ #import "UICollectionView+IGListBatchUpdateData.h" -@implementation IGListAdapterUpdater { - BOOL _canBackgroundReload; -} +@implementation IGListAdapterUpdater - (instancetype)init { IGAssertMainThread(); @@ -36,7 +34,7 @@ _insertIndexPaths = [[NSMutableSet alloc] init]; _reloadIndexPaths = [[NSMutableSet alloc] init]; - _canBackgroundReload = [[[UIDevice currentDevice] systemVersion] compare:@"8.3" options:NSNumericSearch] != NSOrderedAscending; + _allowsBackgroundReloading = YES; } return self; } @@ -155,7 +153,8 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray= NSFoundationVersionNumber_iOS_8_3); + if (iOS83OrLater && self.allowsBackgroundReloading && collectionView.window == nil) { [self beginPerformBatchUpdatestoObjects:toObjects]; executeUpdateBlocks(); [self cleanupUpdateBlockState]; diff --git a/Tests/IGListAdapterUpdaterTests.m b/Tests/IGListAdapterUpdaterTests.m index f5c85bb..a553269 100644 --- a/Tests/IGListAdapterUpdaterTests.m +++ b/Tests/IGListAdapterUpdaterTests.m @@ -459,4 +459,50 @@ XCTAssertEqual([collectionView numberOfItemsInSection:1], 4); } +- (void)test_whenCollectionViewNotInWindow_andBackgroundReloadFlag_isSetNO_diffHappens { + self.updater.allowsBackgroundReloading = NO; + [self.collectionView removeFromSuperview]; + + id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; + self.updater.delegate = mockDelegate; + [mockDelegate setExpectationOrderMatters:YES]; + [[mockDelegate expect] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; + [[mockDelegate expect] listAdapterUpdater:self.updater didPerformBatchUpdates:OCMOCK_ANY withCollectionView: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) { + [expectation fulfill]; + }]; + waitExpectation; + [mockDelegate verify]; +} + +- (void)test_whenCollectionViewNotInWindow_andBackgroundReloadFlag_isDefaultYES_diffDoesNotHappen { + [self.collectionView removeFromSuperview]; + + id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterUpdaterDelegate)]; + self.updater.delegate = mockDelegate; + + // NOTE: The current behavior in this case is for the adapter updater + // simply not to call any delegate methods at all. This may change + // in the future, but we configure the mock delegate to allow any call + // except the batch updates calls. + + [[mockDelegate reject] listAdapterUpdater:self.updater willPerformBatchUpdatesWithCollectionView:self.collectionView]; + [[mockDelegate reject] listAdapterUpdater:self.updater didPerformBatchUpdates:OCMOCK_ANY withCollectionView: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) { + [expectation fulfill]; + }]; + waitExpectation; + [mockDelegate verify]; +} + @end