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();
}
}
}