Home asdisplaynode-hierarchy-state
Post
Cancel

asdisplaynode-hierarchy-state

ASDisplayNode.mm

isInHierarchy

1
2
3
4
5
- (BOOL)isInHierarchy
{
  MutexLocker l(__instanceLock__);
  return _flags.isInHierarchy;
}

__enterHierarchy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
- (void)__enterHierarchy
{
  ASDisplayNodeAssertMainThread();
  ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy");
  ASDisplayNodeLogEvent(self, @"enterHierarchy");
  
  // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock.
  __instanceLock__.lock();
  
  if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) {
    _flags.isEnteringHierarchy = YES;
    _flags.isInHierarchy = YES;

    // Don't call -willEnterHierarchy while holding __instanceLock__.
    // This method and subsequent ones (i.e -interfaceState and didEnter(.*)State)
    // don't expect that they are called while the lock is being held.
    // More importantly, didEnter(.*)State methods are meant to be overriden by clients.
    // And so they can potentially walk up the node tree and cause deadlocks, or do expensive tasks and cause the lock to be held for too long.
    __instanceLock__.unlock();
      [self willEnterHierarchy];
      for (ASDisplayNode *subnode in self.subnodes) {
        [subnode __enterHierarchy];
      }
    __instanceLock__.lock();
    
    _flags.isEnteringHierarchy = NO;

    // If we don't have contents finished drawing by the time we are on screen, immediately add the placeholder (if it is enabled and we do have something to draw).
    if (self.contents == nil && [self _implementsDisplay]) {
      CALayer *layer = self.layer;
      [layer setNeedsDisplay];
      
      if ([self _locked_shouldHavePlaceholderLayer]) {
        [CATransaction begin];
        [CATransaction setDisableActions:YES];
        [self _locked_setupPlaceholderLayerIfNeeded];
        _placeholderLayer.opacity = 1.0;
        [CATransaction commit];
        [layer addSublayer:_placeholderLayer];
      }
    }
  }
  
  __instanceLock__.unlock();

  [self didEnterHierarchy];
}

__exitHierarchy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (void)__exitHierarchy
{
  ASDisplayNodeAssertMainThread();
  ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"Should not cause recursive __exitHierarchy");
  ASDisplayNodeLogEvent(self, @"exitHierarchy");
  
  // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock.
  __instanceLock__.lock();
  
  if (_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) {
    _flags.isExitingHierarchy = YES;
    _flags.isInHierarchy = NO;

    // Don't call -didExitHierarchy while holding __instanceLock__. 
    // This method and subsequent ones (i.e -interfaceState and didExit(.*)State)
    // don't expect that they are called while the lock is being held.
    // More importantly, didExit(.*)State methods are meant to be overriden by clients.
    // And so they can potentially walk up the node tree and cause deadlocks, or do expensive tasks and cause the lock to be held for too long.
    __instanceLock__.unlock();
      [self didExitHierarchy];
      for (ASDisplayNode *subnode in self.subnodes) {
        [subnode __exitHierarchy];
      }
    __instanceLock__.lock();
    
    _flags.isExitingHierarchy = NO;
  }
  
  __instanceLock__.unlock();
}

enterHierarchyState

1
2
3
4
5
6
7
8
9
10
- (void)enterHierarchyState:(ASHierarchyState)hierarchyState
{
  if (hierarchyState == ASHierarchyStateNormal) {
    return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing.
  }
  
  ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) {
    node.hierarchyState |= hierarchyState;
  });
}

exitHierarchyState

1
2
3
4
5
6
7
8
9
- (void)exitHierarchyState:(ASHierarchyState)hierarchyState
{
  if (hierarchyState == ASHierarchyStateNormal) {
    return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing.
  }
  ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) {
    node.hierarchyState &= (~hierarchyState);
  });
}

didEnterHierarchy

1
2
3
4
5
6
7
- (void)didEnterHierarchy {
  ASDisplayNodeAssertMainThread();
  ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"You should never call -didEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode");
  ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive");
  ASDisplayNodeAssert(_flags.isInHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive");
  ASAssertUnlocked(__instanceLock__);
}

didExitHierarchy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
- (void)didExitHierarchy
{
  ASDisplayNodeAssertMainThread();
  ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode");
  ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive");
  ASAssertUnlocked(__instanceLock__);

  // This case is important when tearing down hierarchies.  We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for
  // things like data analytics about user content viewing.  We cannot call the method in the dealloc as any incidental retain operations in client code would fail.
  // Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the
  // same runloop.  Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed).
  // TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer
  // integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call.

#if !ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR
  if (![self supportsRangeManagedInterfaceState]) {
    self.interfaceState = ASInterfaceStateNone;
    return;
  }
#endif
  if (ASInterfaceStateIncludesVisible(self.pendingInterfaceState)) {
    void(^exitVisibleInterfaceState)(void) = ^{
      // This block intentionally retains self.
      __instanceLock__.lock();
      unsigned isStillInHierarchy = _flags.isInHierarchy;
      BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState);
      ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible);
      // layer may be thrashed, we need to remember the state so we can reset if it enters in same runloop later.
      _preExitingInterfaceState = _pendingInterfaceState;
      __instanceLock__.unlock();
      if (!isStillInHierarchy && isVisible) {
#if ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR
        if (![self supportsRangeManagedInterfaceState]) {
          newState = ASInterfaceStateNone;
        }
#endif
        self.interfaceState = newState;
      }
    };

    if (!ASCATransactionQueueGet().enabled) {
      dispatch_async(dispatch_get_main_queue(), exitVisibleInterfaceState);
    } else {
      exitVisibleInterfaceState();
    }
  }
}
This post is licensed under CC BY 4.0 by the author.