Skip to content

Commit 0143e32

Browse files
authored
Improve Ancestry Handling, Avoid Assertion Failure (TextureGroup#246)
* Improve our handling of ancestry * Increase chungalunga
1 parent 19ec044 commit 0143e32

8 files changed

Lines changed: 97 additions & 39 deletions

File tree

AsyncDisplayKit.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@
338338
CC57EAF71E3939350034C595 /* ASCollectionView+Undeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = CC2E317F1DAC353700EEE891 /* ASCollectionView+Undeprecated.h */; settings = {ATTRIBUTES = (Private, ); }; };
339339
CC57EAF81E3939450034C595 /* ASTableView+Undeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */; settings = {ATTRIBUTES = (Private, ); }; };
340340
CC58AA4B1E398E1D002C8CB4 /* ASBlockTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
341-
CC6AA2DA1E9F03B900978E87 /* ASDisplayNode+Ancestry.h in Headers */ = {isa = PBXBuildFile; fileRef = CC6AA2D81E9F03B900978E87 /* ASDisplayNode+Ancestry.h */; };
341+
CC6AA2DA1E9F03B900978E87 /* ASDisplayNode+Ancestry.h in Headers */ = {isa = PBXBuildFile; fileRef = CC6AA2D81E9F03B900978E87 /* ASDisplayNode+Ancestry.h */; settings = {ATTRIBUTES = (Public, ); }; };
342342
CC6AA2DB1E9F03B900978E87 /* ASDisplayNode+Ancestry.m in Sources */ = {isa = PBXBuildFile; fileRef = CC6AA2D91E9F03B900978E87 /* ASDisplayNode+Ancestry.m */; };
343343
CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; };
344344
CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; };

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
- Move more properties from ASTableView, ASCollectionView to their respective node classes. [Adlai Holler](https://github.com/Adlai-Holler)
1313
- Remove finalLayoutElement [Michael Schneider] (https://github.com/maicki)[#96](https://github.com/TextureGroup/Texture/pull/96)
1414
- Add ASPageTable - A map table for fast retrieval of objects within a certain page [Huy Nguyen](https://github.com/nguyenhuy)
15+
- Add new public `-supernodes`, `-supernodesIncludingSelf`, and `-supernodeOfClass:includingSelf:` methods. [Adlai Holler](https://github.com/Adlai-Holler)[#246](https://github.com/TextureGroup/Texture/pull/246)
16+
- Improve our handling supernode traversal to avoid loading layers and fix assertion failures you might hit in debug. [Adlai Holler](https://github.com/Adlai-Holler)[#246](https://github.com/TextureGroup/Texture/pull/246)
1517
- [ASDisplayNode] Pass drawParameter in rendering context callbacks [Michael Schneider](https://github.com/maicki)[#248](https://github.com/TextureGroup/Texture/pull/248)
1618
- [ASTextNode] Move to class method of drawRect:withParameters:isCancelled:isRasterizing: for drawing [Michael Schneider] (https://github.com/maicki)[#232](https://github.com/TextureGroup/Texture/pull/232)
1719
- [ASDisplayNode] Remove instance:-drawRect:withParameters:isCancelled:isRasterizing: (https://github.com/maicki)[#232](https://github.com/TextureGroup/Texture/pull/232)

Source/ASDisplayNode.mm

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
1919

20+
#import <AsyncDisplayKit/ASDisplayNode+Ancestry.h>
2021
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
2122
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
2223
#import <AsyncDisplayKit/ASDisplayNode+Deprecated.h>
@@ -600,9 +601,9 @@ - (CALayer *)_locked_layerToLoad
600601
return layer;
601602
}
602603

603-
- (void)_locked_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked
604+
- (void)_locked_loadViewOrLayer
604605
{
605-
if (isLayerBacked) {
606+
if (_flags.layerBacked) {
606607
TIME_SCOPED(_debugTimeToCreateView);
607608
_layer = [self _locked_layerToLoad];
608609
static int ASLayerDelegateAssociationKey;
@@ -692,7 +693,7 @@ - (UIView *)view
692693

693694
// Loading a view needs to happen on the main thread
694695
ASDisplayNodeAssertMainThread();
695-
[self _locked_loadViewOrLayerIsLayerBacked:isLayerBacked];
696+
[self _locked_loadViewOrLayer];
696697

697698
// FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout
698699
// but automatic subnode management would require us to modify the node tree
@@ -727,20 +728,13 @@ - (CALayer *)layer
727728
return _layer;
728729
}
729730

730-
BOOL isLayerBacked = _flags.layerBacked;
731-
if (!isLayerBacked) {
732-
// No need for the lock and call the view explicitly in case it needs to be loaded first
733-
ASDN::MutexUnlocker u(__instanceLock__);
734-
return self.view.layer;
735-
}
736-
737731
if (![self _locked_shouldLoadViewOrLayer]) {
738732
return nil;
739733
}
740734

741735
// Loading a layer needs to happen on the main thread
742736
ASDisplayNodeAssertMainThread();
743-
[self _locked_loadViewOrLayerIsLayerBacked:isLayerBacked];
737+
[self _locked_loadViewOrLayer];
744738

745739
// FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout
746740
// but automatic subnode management would require us to modify the node tree
@@ -3909,8 +3903,8 @@ - (ASEventLog *)eventLog
39093903
[result addObject:@{ @"frame" : [NSValue valueWithCGRect:_pendingViewState.frame] }];
39103904
}
39113905

3912-
// Check supernode so that if we are cell node we don't find self.
3913-
ASCellNode *cellNode = ASDisplayNodeFindFirstSupernodeOfClass(self.supernode, [ASCellNode class]);
3906+
// Check supernode so that if we are a cell node we don't find self.
3907+
ASCellNode *cellNode = [self supernodeOfClass:[ASCellNode class] includingSelf:NO];
39143908
if (cellNode != nil) {
39153909
[result addObject:@{ @"cellNode" : ASObjectDescriptionMakeTiny(cellNode) }];
39163910
}

Source/ASDisplayNodeExtras.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,12 @@ extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, BOOL tr
125125
/**
126126
Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block.
127127
*/
128-
extern ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisplayNode *node)) AS_WARN_UNUSED_RESULT;
128+
extern ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisplayNode *node)) AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use the `supernodes` property instead.");
129129

130130
/**
131131
Given a display node, traverses up the layer tree hierarchy, returning the first display node of kind class.
132132
*/
133-
extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT;
133+
extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use the `supernodeOfClass:includingSelf:` method instead.");
134134

135135
/**
136136
* Given a layer, find the window it lives in, if any.

Source/ASDisplayNodeExtras.mm

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
1919
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
2020
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
21+
#import <AsyncDisplayKit/ASDisplayNode+Ancestry.h>
2122

2223
#import <queue>
2324
#import <AsyncDisplayKit/ASRunLoopQueue.h>
@@ -138,24 +139,21 @@ extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, BOOL tr
138139

139140
ASDisplayNode *ASDisplayNodeFindFirstSupernode(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
140141
{
141-
CALayer *layer = node.layer;
142-
143-
while (layer) {
144-
node = ASLayerToDisplayNode(layer);
145-
if (block(node)) {
146-
return node;
142+
// This function has historically started with `self` but the name suggests
143+
// that it wouldn't. Perhaps we should change the behavior.
144+
for (ASDisplayNode *ancestor in node.supernodesIncludingSelf) {
145+
if (block(ancestor)) {
146+
return ancestor;
147147
}
148-
layer = layer.superlayer;
149148
}
150-
151149
return nil;
152150
}
153151

154152
__kindof ASDisplayNode *ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c)
155153
{
156-
return ASDisplayNodeFindFirstSupernode(start, ^(ASDisplayNode *n) {
157-
return [n isKindOfClass:c];
158-
});
154+
// This function has historically started with `self` but the name suggests
155+
// that it wouldn't. Perhaps we should change the behavior.
156+
return [start supernodeOfClass:c includingSelf:YES];
159157
}
160158

161159
static void _ASCollectDisplayNodes(NSMutableArray *array, CALayer *layer)

Source/AsyncDisplayKit.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#import <AsyncDisplayKit/ASAvailability.h>
1919
#import <AsyncDisplayKit/ASDisplayNode.h>
20+
#import <AsyncDisplayKit/ASDisplayNode+Ancestry.h>
2021
#import <AsyncDisplayKit/ASDisplayNode+Convenience.h>
2122
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
2223

Source/Base/ASDisplayNode+Ancestry.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,36 @@ NS_ASSUME_NONNULL_BEGIN
2222

2323
@interface ASDisplayNode (Ancestry)
2424

25-
- (NSEnumerator *)ancestorEnumeratorWithSelf:(BOOL)includeSelf;
25+
/**
26+
* Returns an object to enumerate the supernode ancestry of this node, starting with its supernode.
27+
*
28+
* For instance, you could write:
29+
* for (ASDisplayNode *node in self.supernodes) {
30+
* if ([node.backgroundColor isEqual:[UIColor blueColor]]) {
31+
* node.hidden = YES;
32+
* }
33+
* }
34+
*
35+
* Note: If this property is read on the main thread, the enumeration will attempt to go up
36+
* the layer hierarchy if it finds a break in the display node hierarchy.
37+
*/
38+
@property (atomic, readonly) id<NSFastEnumeration> supernodes;
39+
40+
/**
41+
* Same as `supernodes` but begins the enumeration with self.
42+
*/
43+
@property (atomic, readonly) id<NSFastEnumeration> supernodesIncludingSelf;
44+
45+
/**
46+
* Searches the supernodes of this node for one matching the given class.
47+
*
48+
* @param supernodeClass The class of node you're looking for.
49+
* @param includeSelf Whether to include self in the search.
50+
* @return A node of the given class that is an ancestor of this node, or nil.
51+
*
52+
* @note See the documentation on `supernodes` for details about the upward traversal.
53+
*/
54+
- (nullable __kindof ASDisplayNode *)supernodeOfClass:(Class)supernodeClass includingSelf:(BOOL)includeSelf;
2655

2756
/**
2857
* e.g. "(<MYTextNode: 0xFFFF>, <MYTextContainingNode: 0xFFFF>, <MYCellNode: 0xFFFF>)"

Source/Base/ASDisplayNode+Ancestry.m

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,46 +16,80 @@
1616
//
1717

1818
#import "ASDisplayNode+Ancestry.h"
19+
#import <AsyncDisplayKit/ASThread.h>
20+
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
1921

2022
AS_SUBCLASSING_RESTRICTED
2123
@interface ASNodeAncestryEnumerator : NSEnumerator
2224
@end
2325

2426
@implementation ASNodeAncestryEnumerator {
25-
/// Would be nice to use __unsafe_unretained but nodes can be
26-
/// deallocated on arbitrary threads so nope.
27-
__weak ASDisplayNode * _nextNode;
27+
ASDisplayNode *_lastNode; // This needs to be strong because enumeration will not retain the current batch of objects
28+
BOOL _initialState;
2829
}
2930

3031
- (instancetype)initWithNode:(ASDisplayNode *)node
3132
{
3233
if (self = [super init]) {
33-
_nextNode = node;
34+
_initialState = YES;
35+
_lastNode = node;
3436
}
3537
return self;
3638
}
3739

3840
- (id)nextObject
3941
{
40-
ASDisplayNode *node = _nextNode;
41-
_nextNode = [node supernode];
42-
return node;
42+
if (_initialState) {
43+
_initialState = NO;
44+
return _lastNode;
45+
}
46+
47+
ASDisplayNode *nextNode = _lastNode.supernode;
48+
if (nextNode == nil && ASDisplayNodeThreadIsMain()) {
49+
CALayer *layer = _lastNode.nodeLoaded ? _lastNode.layer.superlayer : nil;
50+
while (layer != nil) {
51+
nextNode = ASLayerToDisplayNode(layer);
52+
if (nextNode != nil) {
53+
break;
54+
}
55+
layer = layer.superlayer;
56+
}
57+
}
58+
_lastNode = nextNode;
59+
return nextNode;
4360
}
4461

4562
@end
4663

4764
@implementation ASDisplayNode (Ancestry)
4865

49-
- (NSEnumerator *)ancestorEnumeratorWithSelf:(BOOL)includeSelf
66+
- (id<NSFastEnumeration>)supernodes
67+
{
68+
NSEnumerator *result = [[ASNodeAncestryEnumerator alloc] initWithNode:self];
69+
[result nextObject]; // discard first object (self)
70+
return result;
71+
}
72+
73+
- (id<NSFastEnumeration>)supernodesIncludingSelf
5074
{
51-
ASDisplayNode *node = includeSelf ? self : self.supernode;
52-
return [[ASNodeAncestryEnumerator alloc] initWithNode:node];
75+
return [[ASNodeAncestryEnumerator alloc] initWithNode:self];
76+
}
77+
78+
- (nullable __kindof ASDisplayNode *)supernodeOfClass:(Class)supernodeClass includingSelf:(BOOL)includeSelf
79+
{
80+
id<NSFastEnumeration> chain = includeSelf ? self.supernodesIncludingSelf : self.supernodes;
81+
for (ASDisplayNode *ancestor in chain) {
82+
if ([ancestor isKindOfClass:supernodeClass]) {
83+
return ancestor;
84+
}
85+
}
86+
return nil;
5387
}
5488

5589
- (NSString *)ancestryDescription
5690
{
5791
NSMutableArray *strings = [NSMutableArray array];
58-
for (ASDisplayNode *node in [self ancestorEnumeratorWithSelf:YES]) {
92+
for (ASDisplayNode *node in self.supernodes) {
5993
[strings addObject:ASObjectDescriptionMakeTiny(node)];
6094
}
6195
return strings.description;

0 commit comments

Comments
 (0)