Skip to content

Commit

Permalink
Add allowsBackgroundReloading Flag to ListAdapterUpdater to Give Us…
Browse files Browse the repository at this point in the history
…er 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 #375

Differential Revision: D4377586

Pulled By: jessesquires

fbshipit-source-id: c48467ca5a02ab104d1e83b30430b14b186dbdd2
  • Loading branch information
jessesquires authored and Facebook Github Bot committed Jan 3, 2017
1 parent 0de9632 commit 71ce990
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions Source/IGListAdapterUpdater.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
9 changes: 4 additions & 5 deletions Source/IGListAdapterUpdater.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@

#import "UICollectionView+IGListBatchUpdateData.h"

@implementation IGListAdapterUpdater {
BOOL _canBackgroundReload;
}
@implementation IGListAdapterUpdater

- (instancetype)init {
IGAssertMainThread();
Expand All @@ -36,7 +34,7 @@ - (instancetype)init {
_insertIndexPaths = [[NSMutableSet alloc] init];
_reloadIndexPaths = [[NSMutableSet alloc] init];

_canBackgroundReload = [[[UIDevice currentDevice] systemVersion] compare:@"8.3" options:NSNumericSearch] != NSOrderedAscending;
_allowsBackgroundReloading = YES;
}
return self;
}
Expand Down Expand Up @@ -155,7 +153,8 @@ - (void)performBatchUpdatesWithCollectionView:(UICollectionView *)collectionView

// 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];
Expand Down
46 changes: 46 additions & 0 deletions Tests/IGListAdapterUpdaterTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -459,4 +459,50 @@ - (void)test_whenCallingReloadData_withUICollectionViewFlowLayout_withEstimatedS
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

0 comments on commit 71ce990

Please sign in to comment.