Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit 90a1bb2

Browse files
author
Scott Goodson
committed
Ruthlessly improve efficiency in ASRangeControllerBeta.
- Use completedNodes directly, caching inner arrays and counts between loop iterations. - Merge codepaths between the "entire self - table / collection" visible or invisible cases - Ensure we do not trigger an assertion if a previous iteration's node is nil by the time we try to reset its interfaceState.
1 parent 0feaa2a commit 90a1bb2

1 file changed

Lines changed: 69 additions & 50 deletions

File tree

AsyncDisplayKit/Details/ASRangeControllerBeta.mm

Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -80,36 +80,48 @@ - (void)_updateVisibleNodeIndexPaths
8080
[_layoutController setVisibleNodeIndexPaths:visibleNodePaths];
8181
}
8282

83-
ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self];
83+
NSArray *allNodes = [_dataSource completedNodes];
84+
NSArray *currentSectionNodes = nil;
85+
NSInteger currentSectionIndex = -1; // Will be unequal to any indexPath.section, so we set currentSectionNodes.
8486

85-
NSSet *visibleIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
87+
NSUInteger numberOfSections = [allNodes count];
88+
NSUInteger numberOfNodesInSection = 0;
8689

87-
#if RangeControllerLoggingEnabled
88-
NSMutableArray *modified = [NSMutableArray array];
89-
#endif
90+
NSSet *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths];
91+
// = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
92+
NSSet *displayIndexPaths = nil;
93+
NSSet *fetchDataIndexPaths = nil;
94+
NSMutableSet *allIndexPaths = nil;
95+
NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
96+
97+
ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self];
9098

9199
if (ASInterfaceStateIncludesVisible(selfInterfaceState)) {
92100
// If we are already visible, get busy! Better get started on preloading before the user scrolls more...
93-
NSSet *fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData];
94-
NSSet *displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay];
101+
fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData];
102+
displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay];
95103

96104
// Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint.
97-
NSMutableSet *allIndexPaths = [fetchDataIndexPaths mutableCopy];
105+
allIndexPaths = [fetchDataIndexPaths mutableCopy];
98106
[allIndexPaths unionSet:displayIndexPaths];
99107
[allIndexPaths unionSet:visibleIndexPaths];
108+
} else {
109+
allIndexPaths = [visibleIndexPaths mutableCopy];
110+
}
111+
112+
// Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any
113+
// range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic
114+
// scroll or major main thread stall could cause entirely disjoint sets, but we must visit all.
115+
NSSet *allCurrentIndexPaths = [allIndexPaths copy];
116+
[allIndexPaths unionSet:_allPreviousIndexPaths];
117+
_allPreviousIndexPaths = allCurrentIndexPaths;
118+
119+
for (NSIndexPath *indexPath in allIndexPaths) {
120+
// Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it.
121+
// For consistency, make sure each node knows that it should measure itself if something changes.
122+
ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout;
100123

101-
// Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any
102-
// range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic
103-
// scroll or major main thread stall could cause entirely disjoint sets, but we must visit all.
104-
NSSet *allCurrentIndexPaths = [allIndexPaths copy];
105-
[allIndexPaths unionSet:_allPreviousIndexPaths];
106-
_allPreviousIndexPaths = allCurrentIndexPaths;
107-
108-
for (NSIndexPath *indexPath in allIndexPaths) {
109-
// Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it.
110-
// For consistency, make sure each node knows that it should measure itself if something changes.
111-
ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout;
112-
124+
if (ASInterfaceStateIncludesVisible(selfInterfaceState)) {
113125
if ([fetchDataIndexPaths containsObject:indexPath]) {
114126
interfaceState |= ASInterfaceStateFetchData;
115127
}
@@ -119,39 +131,49 @@ - (void)_updateVisibleNodeIndexPaths
119131
if ([visibleIndexPaths containsObject:indexPath]) {
120132
interfaceState |= ASInterfaceStateVisible;
121133
}
122-
123-
ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath];
124-
ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset.");
125-
// Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState.
126-
if (node.interfaceState != interfaceState) {
127-
#if RangeControllerLoggingEnabled
128-
[modified addObject:indexPath];
129-
#endif
130-
[node recursivelySetInterfaceState:interfaceState];
134+
} else {
135+
// If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the
136+
// instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet.
137+
// We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:.
138+
139+
// Set Layout, Fetch Data, Display. DO NOT set Visible: even though these elements are in the visible range / "viewport",
140+
// our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above.
141+
if ([allCurrentIndexPaths containsObject:indexPath]) {
142+
// We might be looking at an indexPath that was previously in-range, but now we need to clear it.
143+
// In that case we'll just set it back to MeasureLayout. Only set Display | FetchData if in allCurrentIndexPaths.
144+
interfaceState |= ASInterfaceStateDisplay;
145+
interfaceState |= ASInterfaceStateFetchData;
131146
}
132147
}
133-
} else {
134-
// If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the
135-
// instant we come onscreen. So, fetch data and display all of those things, but don't waste resources preloading yet.
136-
// We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:.
137148

138-
for (NSIndexPath *indexPath in visibleIndexPaths) {
139-
// Set Layout, Fetch Data, Display. DO NOT set Visible: even though these elements are in the visible range / "viewport",
140-
// our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above.
141-
ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout | ASInterfaceStateFetchData | ASInterfaceStateDisplay;
149+
NSInteger section = indexPath.section;
150+
NSInteger row = indexPath.row;
151+
152+
if (section >= 0 && row >= 0 && section < numberOfSections) {
153+
if (section != currentSectionIndex) {
154+
// Often we'll be dealing with indexPaths in the same section, but the set isn't sorted and we may even bounce
155+
// between the same ones. Still, this saves dozens of method calls to access the inner array and count.
156+
currentSectionNodes = [allNodes objectAtIndex:section];
157+
numberOfNodesInSection = [currentSectionNodes count];
158+
currentSectionIndex = section;
159+
}
142160

143-
ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath];
144-
ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset.");
145-
// Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState.
146-
if (node.interfaceState != interfaceState) {
147-
#if RangeControllerLoggingEnabled
148-
[modified addObject:indexPath];
149-
#endif
150-
[node recursivelySetInterfaceState:interfaceState];
161+
if (row < numberOfNodesInSection) {
162+
ASDisplayNode *node = [currentSectionNodes objectAtIndex:row];
163+
164+
ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset.");
165+
// Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState.
166+
if (node.interfaceState != interfaceState) {
167+
[modifiedIndexPaths addObject:indexPath];
168+
[node recursivelySetInterfaceState:interfaceState];
169+
}
151170
}
152171
}
153172
}
154173

174+
_rangeIsValid = YES;
175+
_queuedRangeUpdate = NO;
176+
155177
#if RangeControllerLoggingEnabled
156178
NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths];
157179
BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet];
@@ -161,9 +183,9 @@ - (void)_updateVisibleNodeIndexPaths
161183
NSLog(@"custom: %@", visibleNodePathsSet);
162184
}
163185

164-
[modified sortUsingSelector:@selector(compare:)];
186+
[modifiedIndexPaths sortUsingSelector:@selector(compare:)];
165187

166-
for (NSIndexPath *indexPath in modified) {
188+
for (NSIndexPath *indexPath in modifiedIndexPaths) {
167189
ASDisplayNode *node = [_dataSource rangeController:self nodeAtIndexPath:indexPath];
168190
ASInterfaceState interfaceState = node.interfaceState;
169191
BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState);
@@ -172,9 +194,6 @@ - (void)_updateVisibleNodeIndexPaths
172194
NSLog(@"indexPath %@, Visible: %d, Display: %d, FetchData: %d", indexPath, inVisible, inDisplay, inFetchData);
173195
}
174196
#endif
175-
176-
_rangeIsValid = YES;
177-
_queuedRangeUpdate = NO;
178197
}
179198

180199
#pragma mark - Cell node view handling

0 commit comments

Comments
 (0)