Make sure range controller listens to node display notifications if absolutely needed

This commit is contained in:
Huy Nguyen
2016-07-07 17:41:58 +07:00
parent c62a4d3e79
commit 312de1a084
3 changed files with 36 additions and 30 deletions

View File

@@ -245,9 +245,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
renderQueue = [[ASRunLoopQueue<ASDisplayNode *> alloc] initWithRunLoop:CFRunLoopGetMain() renderQueue = [[ASRunLoopQueue<ASDisplayNode *> alloc] initWithRunLoop:CFRunLoopGetMain()
andHandler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) { andHandler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) {
CFAbsoluteTime timestamp = isQueueDrained ? CFAbsoluteTimeGetCurrent() : 0;
[dequeuedItem _recursivelyTriggerDisplayAndBlock:NO]; [dequeuedItem _recursivelyTriggerDisplayAndBlock:NO];
if (isQueueDrained) { if (isQueueDrained) {
CFAbsoluteTime timestamp = CFAbsoluteTimeGetCurrent();
[[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification
object:nil object:nil
userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}]; userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}];
@@ -2317,25 +2317,25 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
}); });
} }
- (void)recursivelySetInterfaceState:(ASInterfaceState)interfaceState - (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState
{ {
ASInterfaceState oldState = self.interfaceState;
ASInterfaceState newState = interfaceState;
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
node.interfaceState = interfaceState;
});
if ([self supportsRangeManagedInterfaceState]) {
// Instead of each node in the recursion assuming it needs to schedule itself for display, // Instead of each node in the recursion assuming it needs to schedule itself for display,
// setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set). // setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set).
// If our range manager intends for us to be displayed right now, and didn't before, get started! // If our range manager intends for us to be displayed right now, and didn't before, get started!
BOOL shouldScheduleDisplay = [self supportsRangeManagedInterfaceState] && [self shouldScheduleDisplayWithNewInterfaceState:newInterfaceState];
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState); ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState); node.interfaceState = newInterfaceState;
if (nowDisplay && (nowDisplay != wasDisplay)) { });
if (shouldScheduleDisplay) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self]; [ASDisplayNode scheduleNodeForRecursiveDisplay:self];
} }
} }
- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState
{
BOOL willDisplay = ASInterfaceStateIncludesDisplay(newInterfaceState);
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(self.interfaceState);
return willDisplay && (willDisplay != nowDisplay);
} }
- (ASHierarchyState)hierarchyState - (ASHierarchyState)hierarchyState

View File

@@ -27,7 +27,7 @@
NSSet<NSIndexPath *> *_allPreviousIndexPaths; NSSet<NSIndexPath *> *_allPreviousIndexPaths;
ASLayoutRangeMode _currentRangeMode; ASLayoutRangeMode _currentRangeMode;
BOOL _didUpdateCurrentRange; BOOL _didUpdateCurrentRange;
BOOL _didRegisterForNotifications; BOOL _didRegisterForNodeDisplayNotifications;
CFAbsoluteTime _pendingDisplayNodesTimestamp; CFAbsoluteTime _pendingDisplayNodesTimestamp;
} }
@@ -56,7 +56,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
- (void)dealloc - (void)dealloc
{ {
if (_didRegisterForNotifications) { if (_didRegisterForNodeDisplayNotifications) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil];
} }
} }
@@ -242,10 +242,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
[allIndexPaths addObjectsFromArray:ASIndexPathsForTwoDimensionalArray(allNodes)]; [allIndexPaths addObjectsFromArray:ASIndexPathsForTwoDimensionalArray(allNodes)];
} }
// TODO Don't register for notifications if this range update doesn't cause any node to enter rendering pipeline.
// This can be done once there is an API to observe to (or be notified upon) interface state changes or pipeline enterings
[self registerForNotificationsForInterfaceStateIfNeeded:selfInterfaceState];
#if ASRangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]); ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]);
NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil); NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
@@ -309,15 +305,20 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
#if ASRangeControllerLoggingEnabled #if ASRangeControllerLoggingEnabled
[modifiedIndexPaths addObject:indexPath]; [modifiedIndexPaths addObject:indexPath];
#endif #endif
[node recursivelySetInterfaceState:interfaceState];
}
}
}
}
if (_didRegisterForNotifications) { BOOL nodeShouldScheduleDisplay = [node shouldScheduleDisplayWithNewInterfaceState:interfaceState];
[node recursivelySetInterfaceState:interfaceState];
if (nodeShouldScheduleDisplay) {
[self registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:selfInterfaceState];
if (_didRegisterForNodeDisplayNotifications) {
_pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent(); _pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent();
} }
}
}
}
}
}
_rangeIsValid = YES; _rangeIsValid = YES;
_queuedRangeUpdate = NO; _queuedRangeUpdate = NO;
@@ -338,9 +339,9 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
#pragma mark - Notification observers #pragma mark - Notification observers
- (void)registerForNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState - (void)registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState
{ {
if (!_didRegisterForNotifications) { if (!_didRegisterForNodeDisplayNotifications) {
ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState
currentRangeMode:_currentRangeMode]; currentRangeMode:_currentRangeMode];
if (_currentRangeMode != nextRangeMode) { if (_currentRangeMode != nextRangeMode) {
@@ -348,7 +349,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
selector:@selector(scheduledNodesDidDisplay:) selector:@selector(scheduledNodesDidDisplay:)
name:ASRenderingEngineDidDisplayScheduledNodesNotification name:ASRenderingEngineDidDisplayScheduledNodesNotification
object:nil]; object:nil];
_didRegisterForNotifications = YES; _didRegisterForNodeDisplayNotifications = YES;
} }
} }
} }
@@ -359,7 +360,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
if (_pendingDisplayNodesTimestamp < notificationTimestamp) { if (_pendingDisplayNodesTimestamp < notificationTimestamp) {
// The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update // The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update
[[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil];
_didRegisterForNotifications = NO; _didRegisterForNodeDisplayNotifications = NO;
[self scheduleRangeUpdate]; [self scheduleRangeUpdate];
} }

View File

@@ -135,6 +135,11 @@ inline BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState
*/ */
@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay; @property (nonatomic, assign) BOOL shouldBypassEnsureDisplay;
/**
* @abstract Checks whether a node should be scheduled for display, considering its current and new interface states.
*/
- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState;
@end @end
@interface UIView (ASDisplayNodeInternal) @interface UIView (ASDisplayNodeInternal)