mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-08 13:42:51 +00:00
Scenario: An ASCollectionNode is a subnode of an ASCellNode. A layout transition is started, resulting in the removal of the ASCollectionNode as a subnode. As it is removed, the hierarchy state is cleared - including the "range managed" bit - on the ASCollectionNode. However, the deep recursion traverses the layer hierarchy too, and clears this bit on the ASCellNodes inside the ASCollectionNode. A moment later, the collection performs its final ASRangeController update to mark its cells as invisible and free memory. Then an assertion is triggered in ASRangeController, because it is operating on nodes that do not have the "range managed" bit set. It turns out that ASInterfaceState also propogates in this way, but that behavior is efficient and beneficial in its current configuration (it assists how multi-dimensional preloading works). However, hierarchy state should never need to jump discontinuities in the node hierarchy. For now, disabling that case and will revisit other use cases soon.
277 lines
8.2 KiB
Plaintext
277 lines
8.2 KiB
Plaintext
//
|
|
// ASDisplayNodeExtras.mm
|
|
// AsyncDisplayKit
|
|
//
|
|
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under the BSD-style license found in the
|
|
// LICENSE file in the root directory of this source tree. An additional grant
|
|
// of patent rights can be found in the PATENTS file in the same directory.
|
|
//
|
|
|
|
#import "ASDisplayNodeExtras.h"
|
|
#import "ASDisplayNodeInternal.h"
|
|
#import "ASDisplayNode+FrameworkPrivate.h"
|
|
|
|
#import <queue>
|
|
|
|
extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window)
|
|
{
|
|
ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window");
|
|
if (displayNode && [displayNode supportsRangeManagedInterfaceState]) {
|
|
// Directly clear the visible bit if we are not in a window. This means that the interface state is,
|
|
// if not already, about to be set to invisible as it is not possible for an element to be visible
|
|
// while outside of a window.
|
|
ASInterfaceState interfaceState = displayNode.interfaceState;
|
|
return (window == nil ? (interfaceState &= (~ASInterfaceStateVisible)) : interfaceState);
|
|
} else {
|
|
// For not range managed nodes we might be on our own to try to guess if we're visible.
|
|
return (window == nil ? ASInterfaceStateNone : (ASInterfaceStateVisible | ASInterfaceStateDisplay));
|
|
}
|
|
}
|
|
|
|
extern ASDisplayNode *ASLayerToDisplayNode(CALayer *layer)
|
|
{
|
|
return layer.asyncdisplaykit_node;
|
|
}
|
|
|
|
extern ASDisplayNode *ASViewToDisplayNode(UIView *view)
|
|
{
|
|
return view.asyncdisplaykit_node;
|
|
}
|
|
|
|
extern void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDisplayNode * _Nullable node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node))
|
|
{
|
|
if (!node) {
|
|
ASDisplayNodeCAssertNotNil(layer, @"Cannot recursively perform with nil node and nil layer");
|
|
ASDisplayNodeCAssertMainThread();
|
|
node = ASLayerToDisplayNode(layer);
|
|
}
|
|
|
|
if (node) {
|
|
block(node);
|
|
}
|
|
if (traverseSublayers && !layer && [node isNodeLoaded] && ASDisplayNodeThreadIsMain()) {
|
|
layer = node.layer;
|
|
}
|
|
|
|
if (traverseSublayers && layer && node.shouldRasterizeDescendants == NO) {
|
|
/// NOTE: The docs say `sublayers` returns a copy, but it does not.
|
|
/// See: http://stackoverflow.com/questions/14854480/collection-calayerarray-0x1ed8faa0-was-mutated-while-being-enumerated
|
|
for (CALayer *sublayer in [[layer sublayers] copy]) {
|
|
ASDisplayNodePerformBlockOnEveryNode(sublayer, nil, traverseSublayers, block);
|
|
}
|
|
} else if (node) {
|
|
for (ASDisplayNode *subnode in [node subnodes]) {
|
|
ASDisplayNodePerformBlockOnEveryNode(nil, subnode, traverseSublayers, block);
|
|
}
|
|
}
|
|
}
|
|
|
|
extern void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node))
|
|
{
|
|
// Queue used to keep track of subnodes while traversing this layout in a BFS fashion.
|
|
std::queue<ASDisplayNode *> queue;
|
|
queue.push(node);
|
|
|
|
while (!queue.empty()) {
|
|
node = queue.front();
|
|
queue.pop();
|
|
|
|
block(node);
|
|
|
|
// Add all subnodes to process in next step
|
|
for (ASDisplayNode *subnode in node.subnodes) {
|
|
queue.push(subnode);
|
|
}
|
|
}
|
|
}
|
|
|
|
extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node))
|
|
{
|
|
for (ASDisplayNode *subnode in node.subnodes) {
|
|
ASDisplayNodePerformBlockOnEveryNode(nil, subnode, YES, block);
|
|
}
|
|
}
|
|
|
|
ASDisplayNode *ASDisplayNodeFindFirstSupernode(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
|
|
{
|
|
CALayer *layer = node.layer;
|
|
|
|
while (layer) {
|
|
node = ASLayerToDisplayNode(layer);
|
|
if (block(node)) {
|
|
return node;
|
|
}
|
|
layer = layer.superlayer;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
__kindof ASDisplayNode *ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c)
|
|
{
|
|
return ASDisplayNodeFindFirstSupernode(start, ^(ASDisplayNode *n) {
|
|
return [n isKindOfClass:c];
|
|
});
|
|
}
|
|
|
|
static void _ASCollectDisplayNodes(NSMutableArray *array, CALayer *layer)
|
|
{
|
|
ASDisplayNode *node = ASLayerToDisplayNode(layer);
|
|
|
|
if (nil != node) {
|
|
[array addObject:node];
|
|
}
|
|
|
|
for (CALayer *sublayer in layer.sublayers)
|
|
_ASCollectDisplayNodes(array, sublayer);
|
|
}
|
|
|
|
extern NSArray<ASDisplayNode *> *ASCollectDisplayNodes(ASDisplayNode *node)
|
|
{
|
|
NSMutableArray *list = [NSMutableArray array];
|
|
for (CALayer *sublayer in node.layer.sublayers) {
|
|
_ASCollectDisplayNodes(list, sublayer);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
#pragma mark - Find all subnodes
|
|
|
|
static void _ASDisplayNodeFindAllSubnodes(NSMutableArray *array, ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
|
|
{
|
|
if (!node)
|
|
return;
|
|
|
|
for (ASDisplayNode *subnode in node.subnodes) {
|
|
if (block(subnode)) {
|
|
[array addObject:subnode];
|
|
}
|
|
|
|
_ASDisplayNodeFindAllSubnodes(array, subnode, block);
|
|
}
|
|
}
|
|
|
|
extern NSArray<ASDisplayNode *> *ASDisplayNodeFindAllSubnodes(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node))
|
|
{
|
|
NSMutableArray *list = [NSMutableArray array];
|
|
_ASDisplayNodeFindAllSubnodes(list, start, block);
|
|
return list;
|
|
}
|
|
|
|
extern NSArray<__kindof ASDisplayNode *> *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNode *start, Class c)
|
|
{
|
|
return ASDisplayNodeFindAllSubnodes(start, ^(ASDisplayNode *n) {
|
|
return [n isKindOfClass:c];
|
|
});
|
|
}
|
|
|
|
#pragma mark - Find first subnode
|
|
|
|
static ASDisplayNode *_ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL includeStartNode, BOOL (^block)(ASDisplayNode *node))
|
|
{
|
|
for (ASDisplayNode *subnode in startNode.subnodes) {
|
|
ASDisplayNode *foundNode = _ASDisplayNodeFindFirstNode(subnode, YES, block);
|
|
if (foundNode) {
|
|
return foundNode;
|
|
}
|
|
}
|
|
|
|
if (includeStartNode && block(startNode))
|
|
return startNode;
|
|
|
|
return nil;
|
|
}
|
|
|
|
extern __kindof ASDisplayNode *ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node))
|
|
{
|
|
return _ASDisplayNodeFindFirstNode(startNode, YES, block);
|
|
}
|
|
|
|
extern __kindof ASDisplayNode *ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node))
|
|
{
|
|
return _ASDisplayNodeFindFirstNode(startNode, NO, block);
|
|
}
|
|
|
|
extern __kindof ASDisplayNode *ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c)
|
|
{
|
|
return ASDisplayNodeFindFirstSubnode(start, ^(ASDisplayNode *n) {
|
|
return [n isKindOfClass:c];
|
|
});
|
|
}
|
|
|
|
static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possibleAncestor, ASDisplayNode *possibleDescendant)
|
|
{
|
|
ASDisplayNode *supernode = possibleDescendant;
|
|
while (supernode) {
|
|
if (supernode == possibleAncestor) {
|
|
return YES;
|
|
}
|
|
supernode = supernode.supernode;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
extern ASDisplayNode *ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2)
|
|
{
|
|
ASDisplayNode *possibleAncestor = node1;
|
|
while (possibleAncestor) {
|
|
if (_ASDisplayNodeIsAncestorOfDisplayNode(possibleAncestor, node2)) {
|
|
break;
|
|
}
|
|
possibleAncestor = possibleAncestor.supernode;
|
|
}
|
|
|
|
ASDisplayNodeCAssertNotNil(possibleAncestor, @"Could not find a common ancestor between node1: %@ and node2: %@", node1, node2);
|
|
return possibleAncestor;
|
|
}
|
|
|
|
extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node)
|
|
{
|
|
// node <- supernode on each loop
|
|
// previous <- node on each loop where node is not nil
|
|
// previous is the final non-nil value of supernode, i.e. the root node
|
|
ASDisplayNode *previousNode = node;
|
|
while ((node = [node supernode])) {
|
|
previousNode = node;
|
|
}
|
|
return previousNode;
|
|
}
|
|
|
|
#pragma mark - Placeholders
|
|
|
|
UIColor *ASDisplayNodeDefaultPlaceholderColor()
|
|
{
|
|
static UIColor *defaultPlaceholderColor;
|
|
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
defaultPlaceholderColor = [UIColor colorWithWhite:0.95 alpha:1.0];
|
|
});
|
|
return defaultPlaceholderColor;
|
|
}
|
|
|
|
UIColor *ASDisplayNodeDefaultTintColor()
|
|
{
|
|
static UIColor *defaultTintColor;
|
|
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
defaultTintColor = [UIColor colorWithRed:0.0 green:0.478 blue:1.0 alpha:1.0];
|
|
});
|
|
return defaultTintColor;
|
|
}
|
|
|
|
#pragma mark - Hierarchy Notifications
|
|
|
|
void ASDisplayNodeDisableHierarchyNotifications(ASDisplayNode *node)
|
|
{
|
|
[node __incrementVisibilityNotificationsDisabled];
|
|
}
|
|
|
|
void ASDisplayNodeEnableHierarchyNotifications(ASDisplayNode *node)
|
|
{
|
|
[node __decrementVisibilityNotificationsDisabled];
|
|
}
|