diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b74114..d11cecf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,12 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag - Added `-[IGListSectionController didHighlightItemAtIndex:]` and `-[IGListSectionController didUnhighlightItemAtIndex:]` APIs to support `UICollectionView` cell highlighting. [Kevin Delannoy](https://github.com/delannoyk) [(#933)](https://github.com/Instagram/IGListKit/pull/933) - - Added a new listener API to be notified when `IGListAdapter` finishes updating. Add listeners via `-[IGListAdapter addUpdateListener:]` with objects conforming to the new `IGListAdapterUpdateListener` protocol. [Ryan Nystrom](https://github.com/rnystrom) [(tbd)](https://github.com/Instagram/IGListKit/pull/tbd) - Updated project settings for iOS 11. [Ryan Nystrom](https://github.com/rnystrom) [(#942)](https://github.com/Instagram/IGListKit/pull/942) +- Added experiment to make `-[IGListAdapter visibleSectionControllers:]` a bit faster. [Maxime Ollivier](https://github.com/maxoll) (tbd) + ### Fixes - Duplicate objects for initial data source setup filtered out. [Mikhail Vashlyaev](https://github.com/yemodin) [(#993](https://github.com/Instagram/IGListKit/pull/993) diff --git a/Source/Common/IGListExperiments.h b/Source/Common/IGListExperiments.h index d4893c50..5837aca1 100644 --- a/Source/Common/IGListExperiments.h +++ b/Source/Common/IGListExperiments.h @@ -22,6 +22,8 @@ typedef NS_OPTIONS (NSInteger, IGListExperiment) { IGListExperimentBackgroundDiffing = 1 << 2, /// Test fallback to reloadData when "too many" update operations. IGListExperimentReloadDataFallback = 1 << 3, + /// Test a faster way to return visible section controllers. + IGListExperimentFasterVisibleSectionController = 1 << 4, }; /** diff --git a/Source/IGListAdapter.m b/Source/IGListAdapter.m index 908ebeb9..c82dfea1 100644 --- a/Source/IGListAdapter.m +++ b/Source/IGListAdapter.m @@ -484,6 +484,14 @@ - (NSArray *)visibleSectionControllers { IGAssertMainThread(); + if (IGListExperimentEnabled(self.experiments, IGListExperimentFasterVisibleSectionController)) { + return [self visibleSectionControllersFromDisplayHandler]; + } else { + return [self visibleSectionControllersFromLayoutAttributes]; + } +} + +- (NSArray *)visibleSectionControllersFromLayoutAttributes { NSMutableSet *visibleSectionControllers = [NSMutableSet new]; NSArray *attributes = [self.collectionView.collectionViewLayout layoutAttributesForElementsInRect:self.collectionView.bounds]; @@ -497,6 +505,10 @@ return [visibleSectionControllers allObjects]; } +- (NSArray *)visibleSectionControllersFromDisplayHandler { + return [[self.displayHandler visibleListSections] allObjects]; +} + - (NSArray *)visibleObjects { IGAssertMainThread(); NSArray *visibleCells = [self.collectionView visibleCells]; diff --git a/Source/Internal/IGListDisplayHandler.h b/Source/Internal/IGListDisplayHandler.h index 650a7ca3..8d168050 100644 --- a/Source/Internal/IGListDisplayHandler.h +++ b/Source/Internal/IGListDisplayHandler.h @@ -21,6 +21,11 @@ NS_ASSUME_NONNULL_BEGIN IGLK_SUBCLASSING_RESTRICTED @interface IGListDisplayHandler : NSObject +/** + Counted set of the currently visible section controllers. + */ +@property (nonatomic, strong, readonly) NSCountedSet *visibleListSections; + /** Tells the handler that a cell will be displayed in the IGListAdapter. diff --git a/Source/Internal/IGListDisplayHandler.m b/Source/Internal/IGListDisplayHandler.m index 5e73e7de..9accdfa4 100644 --- a/Source/Internal/IGListDisplayHandler.m +++ b/Source/Internal/IGListDisplayHandler.m @@ -16,7 +16,6 @@ @interface IGListDisplayHandler () -@property (nonatomic, strong) NSCountedSet *visibleListSections; @property (nonatomic, strong) NSMapTable *visibleViewObjectMap; @end diff --git a/Tests/IGListAdapterTests.m b/Tests/IGListAdapterTests.m index 7b781950..101d45b5 100644 --- a/Tests/IGListAdapterTests.m +++ b/Tests/IGListAdapterTests.m @@ -12,6 +12,7 @@ #import #import +#import #import #import "IGListAdapterInternal.h" @@ -262,6 +263,22 @@ XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@4]]); } +- (void)test_whenCellsExtendBeyondBounds_withFasterExperiment_thatVisibleSectionControllersAreLimited { + // add experiment + self.adapter.experiments |= IGListExperimentFasterVisibleSectionController; + // # of items for each object == [item integerValue], so @2 has 2 items (cells) + self.dataSource.objects = @[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12]; + [self.adapter reloadDataWithCompletion:nil]; + XCTAssertEqual([self.collectionView numberOfSections], 12); + NSArray *visibleSectionControllers = [self.adapter visibleSectionControllers]; + // UIWindow is 100x100, each cell is 100x10 so should have the following section/cell count: 1 + 2 + 3 + 4 = 10 (100 tall) + XCTAssertEqual(visibleSectionControllers.count, 4); + XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@1]]); + XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@2]]); + XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@3]]); + XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@4]]); +} + - (void) test_withEmptySectionPlusFooter_thatVisibleSectionControllersAreCorrect { self.dataSource.objects = @[@0]; [self.adapter reloadDataWithCompletion:nil]; @@ -279,6 +296,25 @@ XCTAssertTrue(visibleSectionControllers.firstObject.supplementaryViewSource == supplementarySource); } +- (void) test_withEmptySectionPlusFooter_withFasterExperiment_thatVisibleSectionControllersAreCorrect { + // add experiment + self.adapter.experiments |= IGListExperimentFasterVisibleSectionController; + self.dataSource.objects = @[@0]; + [self.adapter reloadDataWithCompletion:nil]; + IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new]; + supplementarySource.dequeueFromNib = YES; + supplementarySource.collectionContext = self.adapter; + supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionFooter]; + IGListSectionController *controller = [self.adapter sectionControllerForObject:@0]; + controller.supplementaryViewSource = supplementarySource; + supplementarySource.sectionController = controller; + [self.adapter performUpdatesAnimated:NO completion:nil]; + NSArray *visibleSectionControllers = [self.adapter visibleSectionControllers]; + + XCTAssertTrue([visibleSectionControllers count] == 1); + XCTAssertTrue(visibleSectionControllers.firstObject.supplementaryViewSource == supplementarySource); +} + - (void)test_whenCellsExtendBeyondBounds_thatVisibleCellsExistForSectionControllers { self.dataSource.objects = @[@2, @3, @4, @5, @6]; [self.adapter reloadDataWithCompletion:nil];