Add allowsBackgroundReloading Flag to ListAdapterUpdater to Give User Control of Behavior

Summary:
Such a powerful framework y'all have made! Game status: changed.

- Add a flag to `ListAdapterUpdater` to require it to perform diffing, even when collection view is not in a window.
  - Ensures delegate can rely on diffing callbacks.
  - Ensures layout can rely on `prepareForCollectionViewUpdates:`.
  - Helps with support for AsyncDisplayKit that I'm working on ?

- [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.
- [x] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md)
Closes https://github.com/Instagram/IGListKit/pull/375

Differential Revision: D4377586

Pulled By: jessesquires

fbshipit-source-id: c48467ca5a02ab104d1e83b30430b14b186dbdd2
This commit is contained in:
Jesse Squires
2017-01-03 10:35:38 -08:00
committed by Facebook Github Bot
parent 0de9632c0d
commit 71ce9902ca
4 changed files with 63 additions and 5 deletions

View File

@@ -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)

View File

@@ -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.
*/

View File

@@ -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<id<IGListDiffable
// if the collection view isn't in a visible window, skip diffing and batch updating. execute all transition blocks,
// reload data, execute completion blocks, and get outta here
if (_canBackgroundReload && collectionView.window == nil) {
const BOOL iOS83OrLater = (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_8_3);
if (iOS83OrLater && self.allowsBackgroundReloading && collectionView.window == nil) {
[self beginPerformBatchUpdatestoObjects:toObjects];
executeUpdateBlocks();
[self cleanupUpdateBlockState];

View File

@@ -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