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, ^{
renderQueue = [[ASRunLoopQueue<ASDisplayNode *> alloc] initWithRunLoop:CFRunLoopGetMain()
andHandler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) {
CFAbsoluteTime timestamp = isQueueDrained ? CFAbsoluteTimeGetCurrent() : 0;
[dequeuedItem _recursivelyTriggerDisplayAndBlock:NO];
if (isQueueDrained) {
CFAbsoluteTime timestamp = CFAbsoluteTimeGetCurrent();
[[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification
object:nil
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,
// 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!
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState);
BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState);
if (nowDisplay && (nowDisplay != wasDisplay)) {
BOOL shouldScheduleDisplay = [self supportsRangeManagedInterfaceState] && [self shouldScheduleDisplayWithNewInterfaceState:newInterfaceState];
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
node.interfaceState = newInterfaceState;
});
if (shouldScheduleDisplay) {
[ASDisplayNode scheduleNodeForRecursiveDisplay:self];
}
}
- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState
{
BOOL willDisplay = ASInterfaceStateIncludesDisplay(newInterfaceState);
BOOL nowDisplay = ASInterfaceStateIncludesDisplay(self.interfaceState);
return willDisplay && (willDisplay != nowDisplay);
}
- (ASHierarchyState)hierarchyState

View File

@@ -27,7 +27,7 @@
NSSet<NSIndexPath *> *_allPreviousIndexPaths;
ASLayoutRangeMode _currentRangeMode;
BOOL _didUpdateCurrentRange;
BOOL _didRegisterForNotifications;
BOOL _didRegisterForNodeDisplayNotifications;
CFAbsoluteTime _pendingDisplayNodesTimestamp;
}
@@ -56,7 +56,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
- (void)dealloc
{
if (_didRegisterForNotifications) {
if (_didRegisterForNodeDisplayNotifications) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil];
}
}
@@ -242,10 +242,6 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
[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
ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]);
NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
@@ -309,15 +305,20 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
#if ASRangeControllerLoggingEnabled
[modifiedIndexPaths addObject:indexPath];
#endif
[node recursivelySetInterfaceState:interfaceState];
}
}
}
}
if (_didRegisterForNotifications) {
BOOL nodeShouldScheduleDisplay = [node shouldScheduleDisplayWithNewInterfaceState:interfaceState];
[node recursivelySetInterfaceState:interfaceState];
if (nodeShouldScheduleDisplay) {
[self registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:selfInterfaceState];
if (_didRegisterForNodeDisplayNotifications) {
_pendingDisplayNodesTimestamp = CFAbsoluteTimeGetCurrent();
}
}
}
}
}
}
_rangeIsValid = YES;
_queuedRangeUpdate = NO;
@@ -338,9 +339,9 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
#pragma mark - Notification observers
- (void)registerForNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState
- (void)registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState
{
if (!_didRegisterForNotifications) {
if (!_didRegisterForNodeDisplayNotifications) {
ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState
currentRangeMode:_currentRangeMode];
if (_currentRangeMode != nextRangeMode) {
@@ -348,7 +349,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
selector:@selector(scheduledNodesDidDisplay:)
name:ASRenderingEngineDidDisplayScheduledNodesNotification
object:nil];
_didRegisterForNotifications = YES;
_didRegisterForNodeDisplayNotifications = YES;
}
}
}
@@ -359,7 +360,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
if (_pendingDisplayNodesTimestamp < notificationTimestamp) {
// 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];
_didRegisterForNotifications = NO;
_didRegisterForNodeDisplayNotifications = NO;
[self scheduleRangeUpdate];
}

View File

@@ -135,6 +135,11 @@ inline BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState
*/
@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
@interface UIView (ASDisplayNodeInternal)