Home asdisplaynode-display
Post
Cancel

asdisplaynode-display

ASDisplayNode.mm

1
2
NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes";
NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp";

_locked_displaysAsynchronously

1
2
3
4
5
- (BOOL)_locked_displaysAsynchronously
{
  ASAssertLocked(__instanceLock__);
  return checkFlag(Synchronous) == NO && _flags.displaysAsynchronously;
}

setDisplaysAsynchronously

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously
{
  ASDisplayNodeAssertThreadAffinity(self);
  
  MutexLocker l(__instanceLock__);

  // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel)
  if (checkFlag(Synchronous)) {
    return;
  }

  if (_flags.displaysAsynchronously == displaysAsynchronously) {
    return;
  }

  _flags.displaysAsynchronously = displaysAsynchronously;

  self._locked_asyncLayer.displaysAsynchronously = displaysAsynchronously;
}

rasterizesSubtree

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

enableSubtreeRasterization

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
- (void)enableSubtreeRasterization
{
  MutexLocker l(__instanceLock__);
  // Already rasterized from self.
  if (_flags.rasterizesSubtree) {
    return;
  }

  // If rasterized from above, bail.
  if (ASHierarchyStateIncludesRasterized(_hierarchyState)) {
    ASDisplayNodeFailAssert(@"Subnode of a rasterized node should not have redundant -enableSubtreeRasterization.");
    return;
  }

  // Ensure not loaded.
  if ([self _locked_isNodeLoaded]) {
    ASDisplayNodeFailAssert(@"Cannot call %@ on loaded node: %@", NSStringFromSelector(_cmd), self);
    return;
  }

  // Ensure no loaded subnodes
  ASDisplayNode *loadedSubnode = ASDisplayNodeFindFirstSubnode(self, ^BOOL(ASDisplayNode * _Nonnull node) {
    return node.nodeLoaded;
  });
  if (loadedSubnode != nil) {
      ASDisplayNodeFailAssert(@"Cannot call %@ on node %@ with loaded subnode %@", NSStringFromSelector(_cmd), self, loadedSubnode);
      return;
  }

  _flags.rasterizesSubtree = YES;

  // Tell subnodes that now they're in a rasterized hierarchy (while holding lock!)
  for (ASDisplayNode *subnode in _subnodes) {
    [subnode enterHierarchyState:ASHierarchyStateRasterized];
  }
}

contentsScaleForDisplay

- (CGFloat)contentsScaleForDisplay
{
  MutexLocker l(__instanceLock__);

  return _contentsScaleForDisplay;
}

displayImmediately

1
2
3
4
5
6
7
- (void)displayImmediately
{
  ASDisplayNodeAssertMainThread();
  ASDisplayNodeAssert(!checkFlag(Synchronous), @"this method is designed for asynchronous mode only");

  [self.asyncLayer displayImmediately];
}

recursivelyDisplayImmediately

1
2
3
4
5
6
7
- (void)recursivelyDisplayImmediately
{
  for (ASDisplayNode *child in self.subnodes) {
    [child recursivelyDisplayImmediately];
  }
  [self displayImmediately];
}

__setNeedsDisplay

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)__setNeedsDisplay
{
  BOOL shouldScheduleForDisplay = NO;
  {
    MutexLocker l(__instanceLock__);
    BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState);
    // FIXME: This should not need to recursively display, so create a non-recursive variant.
    // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive.
    if (_layer != nil && !checkFlag(Synchronous) && nowDisplay && [self _implementsDisplay]) {
      shouldScheduleForDisplay = YES;
    }
  }
  
  if (shouldScheduleForDisplay) {
    [ASDisplayNode scheduleNodeForRecursiveDisplay:self];
  }
}

scheduleNodeForRecursiveDisplay

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node
{
  static dispatch_once_t onceToken;
  static ASRunLoopQueue<ASDisplayNode *> *renderQueue;
  dispatch_once(&onceToken, ^{
    renderQueue = [[ASRunLoopQueue<ASDisplayNode *> alloc] initWithRunLoop:CFRunLoopGetMain()
                                                             retainObjects:NO
                                                                   handler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) {
      [dequeuedItem _recursivelyTriggerDisplayAndBlock:NO];
      if (isQueueDrained) {
        CFTimeInterval timestamp = CACurrentMediaTime();
        [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification
                                                            object:nil
                                                          userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}];
      }
    }];
  });

  as_log_verbose(ASDisplayLog(), "%s %@", sel_getName(_cmd), node);
  [renderQueue enqueue:node];
}

_implementsDisplay

1
2
3
4
5
6
- (BOOL)_implementsDisplay
{
  MutexLocker l(__instanceLock__);
  
  return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.rasterizesSubtree;
}

_pendingNodeWillDisplay

1
2
3
4
5
6
7
8
9
10
11
12
13
// Track that a node will be displayed as part of the current node hierarchy.
// The node sending the message should usually be passed as the parameter, similar to the delegation pattern.
- (void)_pendingNodeWillDisplay:(ASDisplayNode *)node
{
  ASDisplayNodeAssertMainThread();

  // No lock needed as _pendingDisplayNodes is main thread only
  if (!_pendingDisplayNodes) {
    _pendingDisplayNodes = [[ASWeakSet alloc] init];
  }

  [_pendingDisplayNodes addObject:node];
}

_pendingNodeDidDisplay

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
// Notify that a node that was pending display finished
// The node sending the message should usually be passed as the parameter, similar to the delegation pattern.
- (void)_pendingNodeDidDisplay:(ASDisplayNode *)node
{
  ASDisplayNodeAssertMainThread();

  // No lock for _pendingDisplayNodes needed as it's main thread only
  [_pendingDisplayNodes removeObject:node];

  if (_pendingDisplayNodes.isEmpty) {
    
    [self hierarchyDisplayDidFinish];
    [self enumerateInterfaceStateDelegates:^(id<ASInterfaceStateDelegate> delegate) {
      [delegate hierarchyDisplayDidFinish];
    }];
      
    BOOL placeholderShouldPersist = [self placeholderShouldPersist];

    __instanceLock__.lock();
    if (_placeholderLayer.superlayer && !placeholderShouldPersist) {
      void (^cleanupBlock)() = ^{
        [_placeholderLayer removeFromSuperlayer];
      };

      if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) {
        [CATransaction begin];
        [CATransaction setCompletionBlock:cleanupBlock];
        [CATransaction setAnimationDuration:_placeholderFadeDuration];
        _placeholderLayer.opacity = 0.0;
        [CATransaction commit];
      } else {
        cleanupBlock();
      }
    }
    __instanceLock__.unlock();
  }
}

_canCallSetNeedsDisplayOfLayer

1
2
3
4
5
6
7
// Helper method to determine if it's safe to call setNeedsDisplay on a layer without throwing away the content.
// For details look at the comment on the canCallSetNeedsDisplayOfLayer flag
- (BOOL)_canCallSetNeedsDisplayOfLayer
{
  MutexLocker l(__instanceLock__);
  return _flags.canCallSetNeedsDisplayOfLayer;
}

recursivelyTriggerDisplayForLayer

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
void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
{
  // This recursion must handle layers in various states:
  // 1. Just added to hierarchy, CA hasn't yet called -display
  // 2. Previously in a hierarchy (such as a working window owned by an Intelligent Preloading class, like ASTableView / ASCollectionView / ASViewController)
  // 3. Has no content to display at all
  // Specifically for case 1), we need to explicitly trigger a -display call now.
  // Otherwise, there is no opportunity to block the main thread after CoreAnimation's transaction commit
  // (even a runloop observer at a late call order will not stop the next frame from compositing, showing placeholders).
  
  ASDisplayNode *node = [layer asyncdisplaykit_node];
  
  if (node.isSynchronous && [node _canCallSetNeedsDisplayOfLayer]) {
    // Layers for UIKit components that are wrapped within a node needs to be set to be displayed as the contents of
    // the layer get's cleared and would not be recreated otherwise.
    // We do not call this for _ASDisplayLayer as an optimization.
    [layer setNeedsDisplay];
  }
  
  if ([node _implementsDisplay]) {
    // For layers that do get displayed here, this immediately kicks off the work on the concurrent -[_ASDisplayLayer displayQueue].
    // At the same time, it creates an associated _ASAsyncTransaction, which we can use to block on display completion.  See ASDisplayNode+AsyncDisplay.mm.
    [layer displayIfNeeded];
  }
  
  // Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work.
  // NOTE: The docs report that `sublayers` returns a copy but it actually doesn't.
  for (CALayer *sublayer in [layer.sublayers copy]) {
    recursivelyTriggerDisplayForLayer(sublayer, shouldBlock);
  }
  
  if (shouldBlock) {
    // As the recursion unwinds, verify each transaction is complete and block if it is not.
    // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first.
    BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay);
    if (waitUntilComplete) {
      for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) {
        // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main.
        // This significantly reduces time on the main thread relative to UIKit.
        [transaction waitUntilComplete];
      }
    }
  }
}

_recursivelyTriggerDisplayAndBlock

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)_recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock
{
  ASDisplayNodeAssertMainThread();
  
  CALayer *layer = self.layer;
  // -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout,
  // so we should call it outside of starting the recursion below.  If our own layer is not marked
  // as dirty, we can assume layout has run on this subtree before.
  if ([layer needsLayout]) {
    [layer layoutIfNeeded];
  }
  recursivelyTriggerDisplayForLayer(layer, shouldBlock);
}

recursivelySetNeedsDisplayAtScale

1
2
3
4
5
6
- (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale
{
  ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) {
    [node setNeedsDisplayAtScale:contentsScale];
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)_layoutClipCornersIfNeeded
{
  ASDisplayNodeAssertMainThread();
  if (_clipCornerLayers[0] == nil) {
    return;
  }
  
  CGSize boundsSize = self.bounds.size;
  for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) {
    BOOL isTop   = (idx == 0 || idx == 1);
    BOOL isRight = (idx == 1 || idx == 2);
    if (_clipCornerLayers[idx]) {
      // Note the Core Animation coordinates are reversed for y; 0 is at the bottom.
      _clipCornerLayers[idx].position = CGPointMake(isRight ? boundsSize.width : 0.0, isTop ? boundsSize.height : 0.0);
      [_layer addSublayer:_clipCornerLayers[idx]];
    }
  }
}

_updateClipCornerLayerContentsWithRadius

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
- (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor:(UIColor *)backgroundColor
{
  ASPerformBlockOnMainThread(^{
    for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) {
      // Layers are, in order: Top Left, Top Right, Bottom Right, Bottom Left.
      // anchorPoint is Bottom Left at 0,0 and Top Right at 1,1.
      BOOL isTop   = (idx == 0 || idx == 1);
      BOOL isRight = (idx == 1 || idx == 2);
      
      CGSize size = CGSizeMake(radius + 1, radius + 1);
      ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay);
      
      CGContextRef ctx = UIGraphicsGetCurrentContext();
      if (isRight == YES) {
        CGContextTranslateCTM(ctx, -radius + 1, 0);
      }
      if (isTop == YES) {
        CGContextTranslateCTM(ctx, 0, -radius + 1);
      }
      UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius];
      [roundedRect setUsesEvenOddFillRule:YES];
      [roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]];
      [backgroundColor setFill];
      [roundedRect fill];
      
      // No lock needed, as _clipCornerLayers is only modified on the main thread.
      CALayer *clipCornerLayer = _clipCornerLayers[idx];
      clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage);
      clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height);
      clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0);
    }
    [self _layoutClipCornersIfNeeded];
  });
}

_setClipCornerLayersVisible

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
- (void)_setClipCornerLayersVisible:(BOOL)visible
{
  ASPerformBlockOnMainThread(^{
    ASDisplayNodeAssertMainThread();
    if (visible) {
      for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) {
        if (_clipCornerLayers[idx] == nil) {
          static ASDisplayNodeCornerLayerDelegate *clipCornerLayers;
          static dispatch_once_t onceToken;
          dispatch_once(&onceToken, ^{
            clipCornerLayers = [[ASDisplayNodeCornerLayerDelegate alloc] init];
          });
          _clipCornerLayers[idx] = [[CALayer alloc] init];
          _clipCornerLayers[idx].zPosition = 99999;
          _clipCornerLayers[idx].delegate = clipCornerLayers;
        }
      }
      [self _updateClipCornerLayerContentsWithRadius:_cornerRadius backgroundColor:self.backgroundColor];
    } else {
      for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) {
        [_clipCornerLayers[idx] removeFromSuperlayer];
        _clipCornerLayers[idx] = nil;
      }
    }
  });
}

updateCornerRoundingWithType

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
- (void)updateCornerRoundingWithType:(ASCornerRoundingType)newRoundingType cornerRadius:(CGFloat)newCornerRadius
{
  __instanceLock__.lock();
    CGFloat oldCornerRadius = _cornerRadius;
    ASCornerRoundingType oldRoundingType = _cornerRoundingType;

    _cornerRadius = newCornerRadius;
    _cornerRoundingType = newRoundingType;
  __instanceLock__.unlock();
 
  ASPerformBlockOnMainThread(^{
    ASDisplayNodeAssertMainThread();
    
    if (oldRoundingType != newRoundingType || oldCornerRadius != newCornerRadius) {
      if (oldRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) {
        if (newRoundingType == ASCornerRoundingTypePrecomposited) {
          self.layerCornerRadius = 0.0;
          if (oldCornerRadius > 0.0) {
            [self displayImmediately];
          } else {
            [self setNeedsDisplay]; // Async display is OK if we aren't replacing an existing .cornerRadius.
          }
        }
        else if (newRoundingType == ASCornerRoundingTypeClipping) {
          self.layerCornerRadius = 0.0;
          [self _setClipCornerLayersVisible:YES];
        } else if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) {
          self.layerCornerRadius = newCornerRadius;
        }
      }
      else if (oldRoundingType == ASCornerRoundingTypePrecomposited) {
        if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) {
          self.layerCornerRadius = newCornerRadius;
          [self setNeedsDisplay];
        }
        else if (newRoundingType == ASCornerRoundingTypePrecomposited) {
          // Corners are already precomposited, but the radius has changed.
          // Default to async re-display.  The user may force a synchronous display if desired.
          [self setNeedsDisplay];
        }
        else if (newRoundingType == ASCornerRoundingTypeClipping) {
          [self _setClipCornerLayersVisible:YES];
          [self setNeedsDisplay];
        }
      }
      else if (oldRoundingType == ASCornerRoundingTypeClipping) {
        if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) {
          self.layerCornerRadius = newCornerRadius;
          [self _setClipCornerLayersVisible:NO];
        }
        else if (newRoundingType == ASCornerRoundingTypePrecomposited) {
          [self _setClipCornerLayersVisible:NO];
          [self displayImmediately];
        }
        else if (newRoundingType == ASCornerRoundingTypeClipping) {
          // Clip corners already exist, but the radius has changed.
          [self _updateClipCornerLayerContentsWithRadius:newCornerRadius backgroundColor:self.backgroundColor];
        }
      }
    }
  });
}

_recursivelySetDisplaySuspended

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
// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or a variant with a condition / test block.
static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, BOOL flag)
{
  // If there is no layer, but node whose its view is loaded, then we can traverse down its layer hierarchy.  Otherwise we must stick to the node hierarchy to avoid loading views prematurely.  Note that for nodes that haven't loaded their views, they can't possibly have subviews/sublayers, so we don't need to traverse the layer hierarchy for them.
  if (!layer && node && node.nodeLoaded) {
    layer = node.layer;
  }

  // If we don't know the node, but the layer is an async layer, get the node from the layer.
  if (!node && layer && [layer isKindOfClass:[_ASDisplayLayer class]]) {
    node = layer.asyncdisplaykit_node;
  }

  // Set the flag on the node.  If this is a pure layer (no node) then this has no effect (plain layers don't support preventing/cancelling display).
  node.displaySuspended = flag;

  if (layer && !node.rasterizesSubtree) {
    // If there is a layer, recurse down the layer hierarchy to set the flag on descendants.  This will cover both layer-based and node-based children.
    for (CALayer *sublayer in layer.sublayers) {
      _recursivelySetDisplaySuspended(nil, sublayer, flag);
    }
  } else {
    // If there is no layer (view not loaded yet) or this node rasterizes descendants (there won't be a layer tree to traverse), recurse down the subnode hierarchy to set the flag on descendants.  This covers only node-based children, but for a node whose view is not loaded it can't possibly have nodeless children.
    for (ASDisplayNode *subnode in node.subnodes) {
      _recursivelySetDisplaySuspended(subnode, nil, flag);
    }
  }
}

setDisplaySuspended

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
- (void)setDisplaySuspended:(BOOL)flag
{
  ASDisplayNodeAssertThreadAffinity(self);
  __instanceLock__.lock();

  // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel)
  if (checkFlag(Synchronous) || _flags.displaySuspended == flag) {
    __instanceLock__.unlock();
    return;
  }

  _flags.displaySuspended = flag;

  self._locked_asyncLayer.displaySuspended = flag;
  
  ASDisplayNode *supernode = _supernode;
  __instanceLock__.unlock();

  if ([self _implementsDisplay]) {
    // Display start and finish methods needs to happen on the main thread
    ASPerformBlockOnMainThread(^{
      if (flag) {
        [supernode subnodeDisplayDidFinish:self];
      } else {
        [supernode subnodeDisplayWillStart:self];
      }
    });
  }
}

willDisplayAsyncLayer

1
2
3
4
5
6
7
8
9
10
- (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer asynchronously:(BOOL)asynchronously
{
  // Subclass hook.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
  [self displayWillStart];
#pragma clang diagnostic pop

  [self displayWillStartAsynchronously:asynchronously];
}

didDisplayAsyncLayer

1
2
3
4
5
- (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer
{
  // Subclass hook.
  [self displayDidFinish];
}

displayWillStart

1
- (void)displayWillStart {}

displayWillStartAsynchronously

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)displayWillStartAsynchronously:(BOOL)asynchronously
{
  ASDisplayNodeAssertMainThread();

  ASDisplayNodeLogEvent(self, @"displayWillStart");
  // in case current node takes longer to display than it's subnodes, treat it as a dependent node
  [self _pendingNodeWillDisplay:self];
  
  __instanceLock__.lock();
  ASDisplayNode *supernode = _supernode;
  __instanceLock__.unlock();
  
  [supernode subnodeDisplayWillStart:self];
}

displayDidFinish

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)displayDidFinish
{
  ASDisplayNodeAssertMainThread();
  
  ASDisplayNodeLogEvent(self, @"displayDidFinish");
  [self _pendingNodeDidDisplay:self];

  __instanceLock__.lock();
  ASDisplayNode *supernode = _supernode;
  __instanceLock__.unlock();
  
  [supernode subnodeDisplayDidFinish:self];
}

subnodeDisplayWillStart

1
2
3
4
5
- (void)subnodeDisplayWillStart:(ASDisplayNode *)subnode
{
  // Subclass hook
  [self _pendingNodeWillDisplay:subnode];
}

subnodeDisplayDidFinish

1
2
3
4
5
- (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode
{
  // Subclass hook
  [self _pendingNodeDidDisplay:subnode];
}

actionForLayer

1
2
3
4
5
6
7
8
9
10
11
12
// We are only the delegate for the layer when we are layer-backed, as UIView performs this function normally
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
  if (event == kCAOnOrderIn) {
    [self __enterHierarchy];
  } else if (event == kCAOnOrderOut) {
    [self __exitHierarchy];
  }

  ASDisplayNodeAssert(_flags.layerBacked, @"We shouldn't get called back here unless we are layer-backed.");
  return (id)kCFNull;
}
This post is licensed under CC BY 4.0 by the author.