IGListKit/Source/Internal/IGListWorkingRangeHandler.mm
FBShipIt cdc796746a Initial commit
fbshipit-source-id: c38b897b9e2b87c782e0e01b0145d9189dc7f9d8
2016-10-11 10:12:17 -04:00

160 lines
6 KiB
Text

/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "IGListWorkingRangeHandler.h"
#import <set>
#import <unordered_set>
#import <vector>
#import <IGListKit/IGListAssert.h>
#import <IGListKit/IGListAdapter.h>
#import <IGListKit/IGListSectionController.h>
#import <IGListKit/IGListWorkingRangeDelegate.h>
#import "IGListWorkingRangeDelegate.h"
struct _IGListWorkingRangeHandlerIndexPath {
NSInteger section;
NSInteger row;
size_t hash;
bool operator==(const _IGListWorkingRangeHandlerIndexPath &other) const {
return (section == other.section && row == other.row);
}
};
struct _IGListWorkingRangeHandlerSectionControllerWrapper {
IGListSectionController<IGListSectionType> *sectionController;
bool operator==(const _IGListWorkingRangeHandlerSectionControllerWrapper &other) const {
return (sectionController == other.sectionController);
}
};
struct _IGListWorkingRangeHandlerIndexPathHash {
size_t operator()(const _IGListWorkingRangeHandlerIndexPath& o) const {
return (size_t)o.hash;
}
};
struct _IGListWorkingRangeHashID {
size_t operator()(const _IGListWorkingRangeHandlerSectionControllerWrapper &o) const {
return (size_t)[o.sectionController hash];
}
};
typedef std::unordered_set<_IGListWorkingRangeHandlerSectionControllerWrapper, _IGListWorkingRangeHashID> _IGListWorkingRangeSectionControllerSet;
typedef std::unordered_set<_IGListWorkingRangeHandlerIndexPath, _IGListWorkingRangeHandlerIndexPathHash> _IGListWorkingRangeIndexPathSet;
@interface IGListWorkingRangeHandler ()
@property (nonatomic, assign, readonly) NSInteger workingRangeSize;
@end
@implementation IGListWorkingRangeHandler {
_IGListWorkingRangeIndexPathSet _visibleSectionIndices;
_IGListWorkingRangeSectionControllerSet _workingRangeSectionControllers;
}
- (instancetype)initWithWorkingRangeSize:(NSInteger)workingRangeSize {
if (self = [super init]) {
_workingRangeSize = workingRangeSize;
}
return self;
}
- (void)willDisplayItemAtIndexPath:(NSIndexPath *)indexPath
forListAdapter:(IGListAdapter *)listAdapter {
IGParameterAssert(indexPath != nil);
IGParameterAssert(listAdapter != nil);
_visibleSectionIndices.insert({
.section = indexPath.section,
.row = indexPath.row,
.hash = indexPath.hash
});
[self updateWorkingRangesWithListAdapter:listAdapter];
}
- (void)didEndDisplayingItemAtIndexPath:(NSIndexPath *)indexPath
forListAdapter:(IGListAdapter *)listAdapter {
IGParameterAssert(indexPath != nil);
IGParameterAssert(listAdapter != nil);
_visibleSectionIndices.erase({
.section = indexPath.section,
.row = indexPath.row,
.hash = indexPath.hash
});
[self updateWorkingRangesWithListAdapter:listAdapter];
}
#pragma mark - Working Ranges
- (void)updateWorkingRangesWithListAdapter:(IGListAdapter *)listAdapter {
IGAssertMainThread();
// This method is optimized C++ to improve straight-line speed of these operations. Change at your peril.
// We use a std::set because it is ordered.
std::set<NSInteger> visibleSectionSet = std::set<NSInteger>();
for (const _IGListWorkingRangeHandlerIndexPath &indexPath : _visibleSectionIndices) {
visibleSectionSet.insert(indexPath.section);
}
NSInteger start;
NSInteger end;
if (visibleSectionSet.size() == 0) {
// We're now devoid of any visible sections. Bail
start = 0;
end = 0;
} else {
start = MAX(*visibleSectionSet.begin() - _workingRangeSize, 0);
end = MIN(*visibleSectionSet.rbegin() + 1 + _workingRangeSize, (NSInteger)listAdapter.objects.count);
}
// Build the current set of working range section controllers
_IGListWorkingRangeSectionControllerSet workingRangeSectionControllers (visibleSectionSet.size());
for (NSInteger idx = start; idx < end; idx++) {
id item = [listAdapter objectAtSection:idx];
id <IGListSectionType> sectionController = [listAdapter sectionControllerForObject:item];
workingRangeSectionControllers.insert({sectionController});
}
IGAssert(workingRangeSectionControllers.size() < 1000, @"This algorithm is way too slow with so many objects:%lu", workingRangeSectionControllers.size());
// Tell any new section controllers that they have entered the working range
for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : workingRangeSectionControllers) {
// Check if the item exists in the old working range item array.
auto it = _workingRangeSectionControllers.find(wrapper);
if (it == _workingRangeSectionControllers.end()) {
// The section controller isn't in the existing list, so it's new.
id <IGListWorkingRangeDelegate> workingRangeDelegate = wrapper.sectionController.workingRangeDelegate;
[workingRangeDelegate listAdapter:listAdapter sectionControllerWillEnterWorkingRange:wrapper.sectionController];
}
}
// Tell any removed section controllers that they have exited the working range
for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : _workingRangeSectionControllers) {
// Check if the item exists in the new list of section controllers
auto it = workingRangeSectionControllers.find(wrapper);
if (it == workingRangeSectionControllers.end()) {
// If the item does not exist in the new list, then it's been removed.
id <IGListWorkingRangeDelegate> workingRangeDelegate = wrapper.sectionController.workingRangeDelegate;
[workingRangeDelegate listAdapter:listAdapter sectionControllerDidExitWorkingRange:wrapper.sectionController];
}
}
_workingRangeSectionControllers = workingRangeSectionControllers;
}
@end