mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 11:20:18 +00:00
Improve Handling of Rasterized Node Interface & Hierarchy States (#2731)
* Improve handling of rasterize node interface states and testing * Fix harder * Finish fixes and move rasterization flag into beta header * Re-enable rasterization in ASDKgram * Re-enable working test * Only do it in debug
This commit is contained in:
parent
016d99f420
commit
5b80a641af
@ -123,6 +123,28 @@ typedef struct {
|
|||||||
*/
|
*/
|
||||||
+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode;
|
+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Whether to draw all descendant nodes' layers/views into this node's layer/view's backing store.
|
||||||
|
*
|
||||||
|
* @discussion
|
||||||
|
* When set to YES, causes all descendant nodes' layers/views to be drawn directly into this node's layer/view's backing
|
||||||
|
* store. Defaults to NO.
|
||||||
|
*
|
||||||
|
* If a node's descendants are static (never animated or never change attributes after creation) then that node is a
|
||||||
|
* good candidate for rasterization. Rasterizing descendants has two main benefits:
|
||||||
|
* 1) Backing stores for descendant layers are not created. Instead the layers are drawn directly into the rasterized
|
||||||
|
* container. This can save a great deal of memory.
|
||||||
|
* 2) Since the entire subtree is drawn into one backing store, compositing and blending are eliminated in that subtree
|
||||||
|
* which can help improve animation/scrolling/etc performance.
|
||||||
|
*
|
||||||
|
* Rasterization does not currently support descendants with transform, sublayerTransform, or alpha. Those properties
|
||||||
|
* will be ignored when rasterizing descendants.
|
||||||
|
*
|
||||||
|
* Note: this has nothing to do with -[CALayer shouldRasterize], which doesn't work with ASDisplayNode's asynchronous
|
||||||
|
* rendering model.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, assign) BOOL shouldRasterizeDescendants;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@ -416,28 +416,6 @@ extern NSInteger const ASDefaultDrawingPriority;
|
|||||||
*/
|
*/
|
||||||
@property (nonatomic, assign) BOOL displaysAsynchronously;
|
@property (nonatomic, assign) BOOL displaysAsynchronously;
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract Whether to draw all descendant nodes' layers/views into this node's layer/view's backing store.
|
|
||||||
*
|
|
||||||
* @discussion
|
|
||||||
* When set to YES, causes all descendant nodes' layers/views to be drawn directly into this node's layer/view's backing
|
|
||||||
* store. Defaults to NO.
|
|
||||||
*
|
|
||||||
* If a node's descendants are static (never animated or never change attributes after creation) then that node is a
|
|
||||||
* good candidate for rasterization. Rasterizing descendants has two main benefits:
|
|
||||||
* 1) Backing stores for descendant layers are not created. Instead the layers are drawn directly into the rasterized
|
|
||||||
* container. This can save a great deal of memory.
|
|
||||||
* 2) Since the entire subtree is drawn into one backing store, compositing and blending are eliminated in that subtree
|
|
||||||
* which can help improve animation/scrolling/etc performance.
|
|
||||||
*
|
|
||||||
* Rasterization does not currently support descendants with transform, sublayerTransform, or alpha. Those properties
|
|
||||||
* will be ignored when rasterizing descendants.
|
|
||||||
*
|
|
||||||
* Note: this has nothing to do with -[CALayer shouldRasterize], which doesn't work with ASDisplayNode's asynchronous
|
|
||||||
* rendering model.
|
|
||||||
*/
|
|
||||||
@property (nonatomic, assign) BOOL shouldRasterizeDescendants;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract Prevent the node's layer from displaying.
|
* @abstract Prevent the node's layer from displaying.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1422,6 +1422,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
- (void)setShouldRasterizeDescendants:(BOOL)shouldRasterize
|
- (void)setShouldRasterizeDescendants:(BOOL)shouldRasterize
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertThreadAffinity(self);
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
|
BOOL rasterizedFromSelfOrAncestor = NO;
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
|
||||||
@ -1429,6 +1430,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_flags.shouldRasterizeDescendants = shouldRasterize;
|
_flags.shouldRasterizeDescendants = shouldRasterize;
|
||||||
|
rasterizedFromSelfOrAncestor = shouldRasterize || ASHierarchyStateIncludesRasterized(_hierarchyState);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.isNodeLoaded) {
|
if (self.isNodeLoaded) {
|
||||||
@ -1438,18 +1440,18 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
[self recursivelyClearContents];
|
[self recursivelyClearContents];
|
||||||
|
|
||||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode *node) {
|
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode *node) {
|
||||||
if (shouldRasterize) {
|
if (rasterizedFromSelfOrAncestor) {
|
||||||
[node enterHierarchyState:ASHierarchyStateRasterized];
|
[node enterHierarchyState:ASHierarchyStateRasterized];
|
||||||
[node __unloadNode];
|
if (node.isNodeLoaded) {
|
||||||
|
[node __unloadNode];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
[node exitHierarchyState:ASHierarchyStateRasterized];
|
[node exitHierarchyState:ASHierarchyStateRasterized];
|
||||||
[node __loadNode];
|
// We can avoid eagerly loading this node. We will load it on-demand as usual.
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!shouldRasterize) {
|
if (!rasterizedFromSelfOrAncestor) {
|
||||||
// At this point all of our subnodes have their layers or views recreated, but we haven't added
|
// If we are not going to rasterize at all, go ahead and set up our view hierarchy.
|
||||||
// them to ours yet. This is because our node is already loaded, and the above recursion
|
|
||||||
// is only performed on our subnodes -- not self.
|
|
||||||
[self _addSubnodeViewsAndLayers];
|
[self _addSubnodeViewsAndLayers];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1460,7 +1462,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode *node) {
|
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode *node) {
|
||||||
if (shouldRasterize) {
|
if (rasterizedFromSelfOrAncestor) {
|
||||||
[node enterHierarchyState:ASHierarchyStateRasterized];
|
[node enterHierarchyState:ASHierarchyStateRasterized];
|
||||||
} else {
|
} else {
|
||||||
[node exitHierarchyState:ASHierarchyStateRasterized];
|
[node exitHierarchyState:ASHierarchyStateRasterized];
|
||||||
@ -1887,6 +1889,7 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
/*
|
/*
|
||||||
* Central private helper method that should eventually be called if submethods add, insert or replace subnodes
|
* Central private helper method that should eventually be called if submethods add, insert or replace subnodes
|
||||||
* You must hold __instanceLock__ to call this.
|
* You must hold __instanceLock__ to call this.
|
||||||
|
* This method is called with thread affinity.
|
||||||
*
|
*
|
||||||
* @param subnode The subnode to insert
|
* @param subnode The subnode to insert
|
||||||
* @param subnodeIndex The index in _subnodes to insert it
|
* @param subnodeIndex The index in _subnodes to insert it
|
||||||
@ -1930,15 +1933,22 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
// This call will apply our .hierarchyState to the new subnode.
|
// This call will apply our .hierarchyState to the new subnode.
|
||||||
// If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState.
|
// If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState.
|
||||||
[subnode __setSupernode:self];
|
[subnode __setSupernode:self];
|
||||||
|
|
||||||
// Don't bother inserting the view/layer if in a rasterized subtree, because there are no layers in the hierarchy and
|
// If this subnode will be rasterized, update its hierarchy state & enter hierarchy if needed
|
||||||
// none of this could possibly work.
|
if (nodeIsInRasterizedTree(self)) {
|
||||||
if (nodeIsInRasterizedTree(self) == NO && self.nodeLoaded) {
|
ASDisplayNodePerformBlockOnEveryNodeBFS(subnode, ^(ASDisplayNode * _Nonnull node) {
|
||||||
// If node is loaded insert the subnode otherwise wait until the node get's loaded
|
[node enterHierarchyState:ASHierarchyStateRasterized];
|
||||||
ASPerformBlockOnMainThread(^{
|
if (node.isNodeLoaded) {
|
||||||
[self _insertSubnodeSubviewOrSublayer:subnode atIndex:sublayerIndex];
|
[node __unloadNode];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
if (_flags.isInHierarchy) {
|
||||||
|
[subnode __enterHierarchy];
|
||||||
|
}
|
||||||
|
} else if (self.nodeLoaded) {
|
||||||
|
// If not rasterizing, and node is loaded insert the subview/sublayer now.
|
||||||
|
[self _insertSubnodeSubviewOrSublayer:subnode atIndex:sublayerIndex];
|
||||||
|
} // Otherwise we will insert subview/sublayer when we get loaded
|
||||||
|
|
||||||
ASDisplayNodeAssert(disableNotifications == shouldDisableNotificationsForMovingBetweenParents(oldParent, self), @"Invariant violated");
|
ASDisplayNodeAssert(disableNotifications == shouldDisableNotificationsForMovingBetweenParents(oldParent, self), @"Invariant violated");
|
||||||
if (disableNotifications) {
|
if (disableNotifications) {
|
||||||
@ -1968,10 +1978,7 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
if (canUseViewAPI(self, subnode)) {
|
if (canUseViewAPI(self, subnode)) {
|
||||||
[_view insertSubview:subnode.view atIndex:idx];
|
[_view insertSubview:subnode.view atIndex:idx];
|
||||||
} else {
|
} else {
|
||||||
#pragma clang diagnostic push
|
[_layer insertSublayer:subnode.layer atIndex:(unsigned int)idx];
|
||||||
#pragma clang diagnostic ignored "-Wconversion"
|
|
||||||
[_layer insertSublayer:subnode.layer atIndex:idx];
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2032,7 +2039,7 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([oldSubnode _deallocSafeSupernode] != self) {
|
if (oldSubnode.supernode != self) {
|
||||||
ASDisplayNodeFailAssert(@"Old Subnode to replace must be a subnode");
|
ASDisplayNodeFailAssert(@"Old Subnode to replace must be a subnode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2076,7 +2083,7 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([below _deallocSafeSupernode] != self) {
|
if (below.supernode != self) {
|
||||||
ASDisplayNodeFailAssert(@"Node to insert below must be a subnode");
|
ASDisplayNodeFailAssert(@"Node to insert below must be a subnode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2101,7 +2108,7 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
|
|
||||||
// If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to
|
// If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to
|
||||||
// insert it will mess up our calculation
|
// insert it will mess up our calculation
|
||||||
if ([subnode _deallocSafeSupernode] == self) {
|
if (subnode.supernode == self) {
|
||||||
NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode];
|
NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode];
|
||||||
if (currentIndexInSubnodes < belowSubnodeIndex) {
|
if (currentIndexInSubnodes < belowSubnodeIndex) {
|
||||||
belowSubnodeIndex--;
|
belowSubnodeIndex--;
|
||||||
@ -2138,7 +2145,7 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([above _deallocSafeSupernode] != self) {
|
if (above.supernode != self) {
|
||||||
ASDisplayNodeFailAssert(@"Node to insert above must be a subnode");
|
ASDisplayNodeFailAssert(@"Node to insert above must be a subnode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2162,7 +2169,7 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
|
|
||||||
// If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to
|
// If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to
|
||||||
// insert it will mess up our calculation
|
// insert it will mess up our calculation
|
||||||
if ([subnode _deallocSafeSupernode] == self) {
|
if (subnode.supernode == self) {
|
||||||
NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode];
|
NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode];
|
||||||
if (currentIndexInSubnodes <= aboveSubnodeIndex) {
|
if (currentIndexInSubnodes <= aboveSubnodeIndex) {
|
||||||
aboveSubnodeIndex--;
|
aboveSubnodeIndex--;
|
||||||
@ -2228,7 +2235,7 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
|
|
||||||
// Don't call self.supernode here because that will retain/autorelease the supernode. This method -_removeSupernode: is often called while tearing down a node hierarchy, and the supernode in question might be in the middle of its -dealloc. The supernode is never messaged, only compared by value, so this is safe.
|
// Don't call self.supernode here because that will retain/autorelease the supernode. This method -_removeSupernode: is often called while tearing down a node hierarchy, and the supernode in question might be in the middle of its -dealloc. The supernode is never messaged, only compared by value, so this is safe.
|
||||||
// The particular issue that triggers this edge case is when a node calls -removeFromSupernode on a subnode from within its own -dealloc method.
|
// The particular issue that triggers this edge case is when a node calls -removeFromSupernode on a subnode from within its own -dealloc method.
|
||||||
if (!subnode || [subnode _deallocSafeSupernode] != self) {
|
if (!subnode || subnode.supernode != self) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2253,8 +2260,6 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
__weak ASDisplayNode *supernode = _supernode;
|
__weak ASDisplayNode *supernode = _supernode;
|
||||||
__weak UIView *view = _view;
|
__weak UIView *view = _view;
|
||||||
__weak CALayer *layer = _layer;
|
__weak CALayer *layer = _layer;
|
||||||
BOOL layerBacked = _flags.layerBacked;
|
|
||||||
BOOL isNodeLoaded = (layer != nil || view != nil);
|
|
||||||
__instanceLock__.unlock();
|
__instanceLock__.unlock();
|
||||||
|
|
||||||
// Clear supernode's reference to us before removing the view from the hierarchy, as _ASDisplayView
|
// Clear supernode's reference to us before removing the view from the hierarchy, as _ASDisplayView
|
||||||
@ -2262,14 +2267,10 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
// This may result in removing the last strong reference, triggering deallocation after this method.
|
// This may result in removing the last strong reference, triggering deallocation after this method.
|
||||||
[supernode _removeSubnode:self];
|
[supernode _removeSubnode:self];
|
||||||
|
|
||||||
if (isNodeLoaded && (supernode == nil || supernode.isNodeLoaded)) {
|
if (view != nil) {
|
||||||
ASPerformBlockOnMainThread(^{
|
[view removeFromSuperview];
|
||||||
if (layerBacked || supernode.layerBacked) {
|
} else if (layer != nil) {
|
||||||
[layer removeFromSuperlayer];
|
[layer removeFromSuperlayer];
|
||||||
} else {
|
|
||||||
[view removeFromSuperview];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2328,12 +2329,10 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) {
|
if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) {
|
||||||
_flags.isEnteringHierarchy = YES;
|
_flags.isEnteringHierarchy = YES;
|
||||||
_flags.isInHierarchy = YES;
|
_flags.isInHierarchy = YES;
|
||||||
|
|
||||||
if (_flags.shouldRasterizeDescendants) {
|
[self willEnterHierarchy];
|
||||||
// Nodes that are descendants of a rasterized container do not have views or layers, and so cannot receive visibility notifications directly via orderIn/orderOut CALayer actions. Manually send visibility notifications to rasterized descendants.
|
for (ASDisplayNode *subnode in self.subnodes) {
|
||||||
[self _recursiveWillEnterHierarchy];
|
[subnode __enterHierarchy];
|
||||||
} else {
|
|
||||||
[self willEnterHierarchy];
|
|
||||||
}
|
}
|
||||||
_flags.isEnteringHierarchy = NO;
|
_flags.isEnteringHierarchy = NO;
|
||||||
|
|
||||||
@ -2369,13 +2368,10 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
|
|
||||||
[self.asyncLayer cancelAsyncDisplay];
|
[self.asyncLayer cancelAsyncDisplay];
|
||||||
|
|
||||||
if (_flags.shouldRasterizeDescendants) {
|
[self didExitHierarchy];
|
||||||
// Nodes that are descendants of a rasterized container do not have views or layers, and so cannot receive visibility notifications directly via orderIn/orderOut CALayer actions. Manually send visibility notifications to rasterized descendants.
|
for (ASDisplayNode *subnode in self.subnodes) {
|
||||||
[self _recursiveDidExitHierarchy];
|
[subnode __exitHierarchy];
|
||||||
} else {
|
|
||||||
[self didExitHierarchy];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_flags.isExitingHierarchy = NO;
|
_flags.isExitingHierarchy = NO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2416,19 +2412,13 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
return ([_subnodes copy] ?: @[]);
|
return ([_subnodes copy] ?: @[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: This method must be dealloc-safe (should not retain self).
|
||||||
- (ASDisplayNode *)supernode
|
- (ASDisplayNode *)supernode
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
return _supernode;
|
return _supernode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a thread-method to return the supernode without causing it to be retained autoreleased. See -_removeSubnode: for details.
|
|
||||||
- (ASDisplayNode *)_deallocSafeSupernode
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return _supernode;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)__setSupernode:(ASDisplayNode *)newSupernode
|
- (void)__setSupernode:(ASDisplayNode *)newSupernode
|
||||||
{
|
{
|
||||||
BOOL supernodeDidChange = NO;
|
BOOL supernodeDidChange = NO;
|
||||||
@ -2471,7 +2461,7 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
|
|
||||||
// Propagate down the new pending transition id
|
// Propagate down the new pending transition id
|
||||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||||
node.pendingTransitionID = _pendingTransitionID;
|
node.pendingTransitionID = pendingTransitionId;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2485,6 +2475,14 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
|||||||
stateToEnterOrExit |= ASHierarchyStateLayoutPending;
|
stateToEnterOrExit |= ASHierarchyStateLayoutPending;
|
||||||
|
|
||||||
[self exitHierarchyState:stateToEnterOrExit];
|
[self exitHierarchyState:stateToEnterOrExit];
|
||||||
|
|
||||||
|
// We only need to explicitly exit hierarchy here if we were rasterized.
|
||||||
|
// Otherwise we will exit the hierarchy when our view/layer does so
|
||||||
|
// which has some nice carry-over machinery to handle cases where we are removed from a hierarchy
|
||||||
|
// and then added into it again shortly after.
|
||||||
|
if (parentWasOrIsRasterized && _flags.isInHierarchy) {
|
||||||
|
[self __exitHierarchy];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3559,12 +3557,6 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority";
|
|||||||
return _flags.isInHierarchy;
|
return _flags.isInHierarchy;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setInHierarchy:(BOOL)inHierarchy
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
_flags.isInHierarchy = inHierarchy;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id<ASLayoutElement>)finalLayoutElement
|
- (id<ASLayoutElement>)finalLayoutElement
|
||||||
{
|
{
|
||||||
return self;
|
return self;
|
||||||
@ -3610,7 +3602,7 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority";
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check supernode so that if we are cell node we don't find self.
|
// Check supernode so that if we are cell node we don't find self.
|
||||||
ASCellNode *cellNode = ASDisplayNodeFindFirstSupernodeOfClass([self _deallocSafeSupernode], [ASCellNode class]);
|
ASCellNode *cellNode = ASDisplayNodeFindFirstSupernodeOfClass(self.supernode, [ASCellNode class]);
|
||||||
if (cellNode != nil) {
|
if (cellNode != nil) {
|
||||||
[result addObject:@{ @"cellNode" : ASObjectDescriptionMakeTiny(cellNode) }];
|
[result addObject:@{ @"cellNode" : ASObjectDescriptionMakeTiny(cellNode) }];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,17 @@
|
|||||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the debugName field for these nodes to the given symbol names, within the domain of "self.class"
|
||||||
|
* For instance, in `MYButtonNode` if you call `ASSetDebugNames(self.titleNode, _countNode)` the debug names
|
||||||
|
* for the nodes will be set to `MYButtonNode.titleNode` and `MYButtonNode.countNode`.
|
||||||
|
*/
|
||||||
|
#if DEBUG
|
||||||
|
#define ASSetDebugNames(...) _ASSetDebugNames(self.class, @"" # __VA_ARGS__, __VA_ARGS__, nil)
|
||||||
|
#else
|
||||||
|
#define ASSetDebugNames(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
/// For deallocation of objects on the main thread across multiple run loops.
|
/// For deallocation of objects on the main thread across multiple run loops.
|
||||||
extern void ASPerformMainThreadDeallocation(_Nullable id object);
|
extern void ASPerformMainThreadDeallocation(_Nullable id object);
|
||||||
|
|
||||||
@ -163,6 +174,9 @@ extern UIColor *ASDisplayNodeDefaultTintColor() AS_WARN_UNUSED_RESULT;
|
|||||||
extern void ASDisplayNodeDisableHierarchyNotifications(ASDisplayNode *node);
|
extern void ASDisplayNodeDisableHierarchyNotifications(ASDisplayNode *node);
|
||||||
extern void ASDisplayNodeEnableHierarchyNotifications(ASDisplayNode *node);
|
extern void ASDisplayNodeEnableHierarchyNotifications(ASDisplayNode *node);
|
||||||
|
|
||||||
|
// Not to be called directly.
|
||||||
|
extern void _ASSetDebugNames(Class _Nonnull owningClass, NSString * _Nonnull names, ASDisplayNode * _Nullable object, ...);
|
||||||
|
|
||||||
ASDISPLAYNODE_EXTERN_C_END
|
ASDISPLAYNODE_EXTERN_C_END
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@ -32,6 +32,24 @@ extern void ASPerformMainThreadDeallocation(_Nullable id object)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern void _ASSetDebugNames(Class _Nonnull owningClass, NSString * _Nonnull names, ASDisplayNode * _Nullable object, ...)
|
||||||
|
{
|
||||||
|
NSString *owningClassName = NSStringFromClass(owningClass);
|
||||||
|
NSArray *nameArray = [names componentsSeparatedByString:@", "];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, object);
|
||||||
|
NSInteger i = 0;
|
||||||
|
for (ASDisplayNode *node = object; node != nil; node = va_arg(args, id), i++) {
|
||||||
|
NSMutableString *symbolName = [nameArray[i] mutableCopy];
|
||||||
|
// Remove any `self.` or `_` prefix
|
||||||
|
[symbolName replaceOccurrencesOfString:@"self." withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, symbolName.length)];
|
||||||
|
[symbolName replaceOccurrencesOfString:@"_" withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, symbolName.length)];
|
||||||
|
node.debugName = [NSString stringWithFormat:@"%@.%@", owningClassName, symbolName];
|
||||||
|
}
|
||||||
|
ASDisplayNodeCAssert(nameArray.count == i, @"Malformed call to ASSetDebugNames: %@", names);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window)
|
extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window)
|
||||||
{
|
{
|
||||||
ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window");
|
ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window");
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
#import "_ASDisplayView.h"
|
#import "_ASDisplayView.h"
|
||||||
#import "ASDisplayNodeExtras.h"
|
#import "ASDisplayNodeExtras.h"
|
||||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||||
|
#import "ASDisplayNode+Beta.h"
|
||||||
|
|
||||||
#pragma mark - UIAccessibilityElement
|
#pragma mark - UIAccessibilityElement
|
||||||
|
|
||||||
|
|||||||
@ -115,7 +115,7 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat
|
|||||||
- (void)exitHierarchyState:(ASHierarchyState)hierarchyState;
|
- (void)exitHierarchyState:(ASHierarchyState)hierarchyState;
|
||||||
|
|
||||||
// Changed before calling willEnterHierarchy / didExitHierarchy.
|
// Changed before calling willEnterHierarchy / didExitHierarchy.
|
||||||
@property (nonatomic, readwrite, assign, getter = isInHierarchy) BOOL inHierarchy;
|
@property (readonly, assign, getter = isInHierarchy) BOOL inHierarchy;
|
||||||
// Call willEnterHierarchy if necessary and set inHierarchy = YES if visibility notifications are enabled on all of its parents
|
// Call willEnterHierarchy if necessary and set inHierarchy = YES if visibility notifications are enabled on all of its parents
|
||||||
- (void)__enterHierarchy;
|
- (void)__enterHierarchy;
|
||||||
// Call didExitHierarchy if necessary and set inHierarchy = NO if visibility notifications are enabled on all of its parents
|
// Call didExitHierarchy if necessary and set inHierarchy = NO if visibility notifications are enabled on all of its parents
|
||||||
|
|||||||
@ -27,6 +27,8 @@
|
|||||||
#import "ASCenterLayoutSpec.h"
|
#import "ASCenterLayoutSpec.h"
|
||||||
#import "ASBackgroundLayoutSpec.h"
|
#import "ASBackgroundLayoutSpec.h"
|
||||||
#import "ASInternalHelpers.h"
|
#import "ASInternalHelpers.h"
|
||||||
|
#import "ASDisplayNodeExtras.h"
|
||||||
|
#import "ASDisplayNode+Beta.h"
|
||||||
|
|
||||||
// Conveniences for making nodes named a certain way
|
// Conveniences for making nodes named a certain way
|
||||||
#define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.debugName = @#n
|
#define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.debugName = @#n
|
||||||
@ -1971,33 +1973,93 @@ static bool stringContainsPointer(NSString *description, id p) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205
|
// Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205
|
||||||
- (void)DISABLED_testThatNodesAreMarkedInvisibleWhenRemovedFromAVisibleRasterizedHierarchy
|
- (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenContainerEntersHierarchy
|
||||||
{
|
{
|
||||||
ASCellNode *supernode = [[ASCellNode alloc] init];
|
ASDisplayNode *supernode = [[ASDisplayNode alloc] init];
|
||||||
supernode.shouldRasterizeDescendants = YES;
|
supernode.shouldRasterizeDescendants = YES;
|
||||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
|
||||||
|
ASSetDebugNames(supernode, subnode);
|
||||||
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||||
[supernode addSubnode:node];
|
[supernode addSubnode:subnode];
|
||||||
[window addSubnode:supernode];
|
[window addSubnode:supernode];
|
||||||
[window makeKeyAndVisible];
|
[window makeKeyAndVisible];
|
||||||
XCTAssertTrue(node.isVisible);
|
XCTAssertTrue(ASHierarchyStateIncludesRasterized(subnode.hierarchyState));
|
||||||
[node removeFromSupernode];
|
XCTAssertTrue(subnode.isVisible);
|
||||||
XCTAssertFalse(node.isVisible);
|
[supernode.view removeFromSuperview];
|
||||||
|
XCTAssertTrue(ASHierarchyStateIncludesRasterized(subnode.hierarchyState));
|
||||||
|
XCTAssertFalse(subnode.isVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205
|
// Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205
|
||||||
- (void)DISABLED_testThatNodesAreMarkedVisibleWhenAddedToARasterizedHierarchyAlreadyOnscreen
|
- (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenAddedToContainerThatIsInHierarchy
|
||||||
{
|
{
|
||||||
ASCellNode *supernode = [[ASCellNode alloc] init];
|
ASDisplayNode *supernode = [[ASDisplayNode alloc] init];
|
||||||
supernode.shouldRasterizeDescendants = YES;
|
supernode.shouldRasterizeDescendants = YES;
|
||||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
|
||||||
|
ASSetDebugNames(supernode, subnode);
|
||||||
|
|
||||||
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||||
[window addSubnode:supernode];
|
[window addSubnode:supernode];
|
||||||
[window makeKeyAndVisible];
|
[window makeKeyAndVisible];
|
||||||
[supernode addSubnode:node];
|
[supernode addSubnode:subnode];
|
||||||
XCTAssertTrue(node.isVisible);
|
XCTAssertTrue(ASHierarchyStateIncludesRasterized(subnode.hierarchyState));
|
||||||
[node removeFromSupernode];
|
XCTAssertTrue(subnode.isVisible);
|
||||||
XCTAssertFalse(node.isVisible);
|
[subnode removeFromSupernode];
|
||||||
|
XCTAssertFalse(ASHierarchyStateIncludesRasterized(subnode.hierarchyState));
|
||||||
|
XCTAssertFalse(subnode.isVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testThatLoadedNodeGetsUnloadedIfSubtreeBecomesRasterized
|
||||||
|
{
|
||||||
|
ASDisplayNode *supernode = [[ASDisplayNode alloc] init];
|
||||||
|
[supernode view];
|
||||||
|
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
|
||||||
|
ASSetDebugNames(supernode, subnode);
|
||||||
|
[supernode addSubnode:subnode];
|
||||||
|
XCTAssertTrue(subnode.nodeLoaded);
|
||||||
|
supernode.shouldRasterizeDescendants = YES;
|
||||||
|
XCTAssertFalse(subnode.nodeLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testThatLoadedNodeGetsUnloadedIfAddedToRasterizedSubtree
|
||||||
|
{
|
||||||
|
ASDisplayNode *supernode = [[ASDisplayNode alloc] init];
|
||||||
|
supernode.shouldRasterizeDescendants = YES;
|
||||||
|
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
|
||||||
|
ASSetDebugNames(supernode, subnode);
|
||||||
|
[subnode view];
|
||||||
|
XCTAssertTrue(subnode.nodeLoaded);
|
||||||
|
[supernode addSubnode:subnode];
|
||||||
|
XCTAssertFalse(subnode.nodeLoaded);
|
||||||
|
XCTAssertTrue(ASHierarchyStateIncludesRasterized(subnode.hierarchyState));
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testThatClearingRasterizationBitMidwayDownTheTreeWorksRight
|
||||||
|
{
|
||||||
|
ASDisplayNode *topNode = [[ASDisplayNode alloc] init];
|
||||||
|
topNode.shouldRasterizeDescendants = YES;
|
||||||
|
ASDisplayNode *middleNode = [[ASDisplayNode alloc] init];
|
||||||
|
middleNode.shouldRasterizeDescendants = YES;
|
||||||
|
ASDisplayNode *bottomNode = [[ASDisplayNode alloc] init];
|
||||||
|
ASSetDebugNames(topNode, middleNode, bottomNode);
|
||||||
|
[middleNode addSubnode:bottomNode];
|
||||||
|
[topNode addSubnode:middleNode];
|
||||||
|
XCTAssertTrue(ASHierarchyStateIncludesRasterized(bottomNode.hierarchyState));
|
||||||
|
XCTAssertTrue(ASHierarchyStateIncludesRasterized(middleNode.hierarchyState));
|
||||||
|
middleNode.shouldRasterizeDescendants = NO;
|
||||||
|
XCTAssertTrue(ASHierarchyStateIncludesRasterized(bottomNode.hierarchyState));
|
||||||
|
XCTAssertTrue(ASHierarchyStateIncludesRasterized(middleNode.hierarchyState));
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testThatRasterizingWrapperNodesIsNotAllowed
|
||||||
|
{
|
||||||
|
ASDisplayNode *rasterizedSupernode = [[ASDisplayNode alloc] init];
|
||||||
|
rasterizedSupernode.shouldRasterizeDescendants = YES;
|
||||||
|
ASDisplayNode *subnode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{
|
||||||
|
return [[UIView alloc] init];
|
||||||
|
}];
|
||||||
|
ASSetDebugNames(rasterizedSupernode, subnode);
|
||||||
|
XCTAssertThrows([rasterizedSupernode addSubnode:subnode]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2011
|
// Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2011
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
#import "ASDisplayNode.h"
|
#import "ASDisplayNode.h"
|
||||||
#import "ASLayoutSpec.h"
|
#import "ASLayoutSpec.h"
|
||||||
#import "ASLayout.h"
|
#import "ASLayout.h"
|
||||||
|
#import "ASDisplayNode+Beta.h"
|
||||||
|
|
||||||
@interface ASTestNode : ASDisplayNode
|
@interface ASTestNode : ASDisplayNode
|
||||||
@property (strong, nonatomic, nullable) ASLayoutSpec *layoutSpecUnderTest;
|
@property (strong, nonatomic, nullable) ASLayoutSpec *layoutSpecUnderTest;
|
||||||
|
|||||||
@ -91,8 +91,8 @@
|
|||||||
_photoDescriptionLabel.maximumNumberOfLines = 3;
|
_photoDescriptionLabel.maximumNumberOfLines = 3;
|
||||||
|
|
||||||
_photoCommentsView = [[CommentsNode alloc] init];
|
_photoCommentsView = [[CommentsNode alloc] init];
|
||||||
// For now disable shouldRasterizeDescendants as it will throw an assertion: 'Node should always be marked invisible before deallocating. ...'
|
|
||||||
//_photoCommentsView.shouldRasterizeDescendants = YES;
|
_photoCommentsView.shouldRasterizeDescendants = YES;
|
||||||
|
|
||||||
// instead of adding everything addSubnode:
|
// instead of adding everything addSubnode:
|
||||||
self.automaticallyManagesSubnodes = YES;
|
self.automaticallyManagesSubnodes = YES;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user