Skip to content

Commit 2a0b9c8

Browse files
author
Scott Goodson
committed
Substantially improve behavior of nested Table & Collection Nodes
This ensures memory cleanup happens correctly and introduces a new test project to support developing new features while stressing tough use cases for correctness.
1 parent a24cfe6 commit 2a0b9c8

30 files changed

Lines changed: 997 additions & 41 deletions

AsyncDisplayKit/ASCollectionNode.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,16 @@ - (ASCollectionView *)view
3030
return (ASCollectionView *)[super view];
3131
}
3232

33+
- (void)clearContents
34+
{
35+
[super clearContents];
36+
[self.view clearContents];
37+
}
38+
39+
- (void)clearFetchedData
40+
{
41+
[super clearFetchedData];
42+
[self.view clearFetchedData];
43+
}
44+
3345
@end

AsyncDisplayKit/ASCollectionView.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,20 @@
281281
*/
282282
- (ASScrollDirection)scrollableDirections;
283283

284+
/**
285+
* Triggers all loaded ASCellNodes to destroy displayed contents (freeing a lot of memory).
286+
*
287+
* @discussion This method should only be called by ASCollectionNode. To be removed in a later release.
288+
*/
289+
- (void)clearContents;
290+
291+
/**
292+
* Triggers all loaded ASCellNodes to purge any data fetched from the network or disk (freeing memory).
293+
*
294+
* @discussion This method should only be called by ASCollectionNode. To be removed in a later release.
295+
*/
296+
- (void)clearFetchedData;
297+
284298
@end
285299

286300

AsyncDisplayKit/ASCollectionView.mm

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,4 +898,24 @@ - (void)nodeDidRelayout:(ASCellNode *)node
898898
[super performBatchUpdates:^{} completion:nil];
899899
}
900900

901+
#pragma mark - Memory Management
902+
903+
- (void)clearContents
904+
{
905+
for (NSArray *section in [_dataController completedNodes]) {
906+
for (ASDisplayNode *node in section) {
907+
[node recursivelyClearContents];
908+
}
909+
}
910+
}
911+
912+
- (void)clearFetchedData
913+
{
914+
for (NSArray *section in [_dataController completedNodes]) {
915+
for (ASDisplayNode *node in section) {
916+
[node recursivelyClearFetchedData];
917+
}
918+
}
919+
}
920+
901921
@end

AsyncDisplayKit/ASDisplayNode.mm

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -747,29 +747,6 @@ - (void)__setNeedsLayout
747747
}
748748
}
749749

750-
- (void)__setSafeFrame:(CGRect)rect
751-
{
752-
ASDisplayNodeAssertThreadAffinity(self);
753-
ASDN::MutexLocker l(_propertyLock);
754-
755-
BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain());
756-
757-
CGPoint origin = (useLayer ? _layer.bounds.origin : self.bounds.origin);
758-
CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint);
759-
760-
CGRect bounds = (CGRect){ origin, rect.size };
761-
CGPoint position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x,
762-
rect.origin.y + rect.size.height * anchorPoint.y);
763-
764-
if (useLayer) {
765-
_layer.bounds = bounds;
766-
_layer.position = position;
767-
} else {
768-
self.bounds = bounds;
769-
self.position = position;
770-
}
771-
}
772-
773750
// These private methods ensure that subclasses are not required to call super in order for _renderingSubnodes to be properly managed.
774751

775752
- (void)__layout
@@ -1715,12 +1692,14 @@ - (void)layout
17151692
}
17161693

17171694
// Assume that _layout was flattened and is 1-level deep.
1695+
ASDisplayNode *subnode = nil;
1696+
CGRect subnodeFrame = CGRectZero;
17181697
for (ASLayout *subnodeLayout in _layout.sublayouts) {
17191698
ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Cached sublayouts must only contain subnodes' layout.");
1720-
[((ASDisplayNode *)subnodeLayout.layoutableObject) __setSafeFrame:CGRectMake(subnodeLayout.position.x,
1721-
subnodeLayout.position.y,
1722-
subnodeLayout.size.width,
1723-
subnodeLayout.size.height)];
1699+
subnodeFrame.origin = subnodeLayout.position;
1700+
subnodeFrame.size = subnodeLayout.size;
1701+
subnode = ((ASDisplayNode *)subnodeLayout.layoutableObject);
1702+
[subnode setFrame:subnodeFrame];
17241703
}
17251704
}
17261705

AsyncDisplayKit/ASTableNode.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,16 @@ - (ASTableView *)view
2525
return (ASTableView *)[super view];
2626
}
2727

28+
- (void)clearContents
29+
{
30+
[super clearContents];
31+
[self.view clearContents];
32+
}
33+
34+
- (void)clearFetchedData
35+
{
36+
[super clearFetchedData];
37+
[self.view clearFetchedData];
38+
}
39+
2840
@end

AsyncDisplayKit/ASTableView.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,20 @@
260260
*/
261261
@property (nonatomic) BOOL automaticallyAdjustsContentOffset;
262262

263+
/**
264+
* Triggers all loaded ASCellNodes to destroy displayed contents (freeing a lot of memory).
265+
*
266+
* @discussion This method should only be called by ASTableNode. To be removed in a later release.
267+
*/
268+
- (void)clearContents;
269+
270+
/**
271+
* Triggers all loaded ASCellNodes to purge any data fetched from the network or disk (freeing memory).
272+
*
273+
* @discussion This method should only be called by ASTableNode. To be removed in a later release.
274+
*/
275+
- (void)clearFetchedData;
276+
263277
@end
264278

265279

AsyncDisplayKit/ASTableView.mm

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,4 +907,24 @@ - (void)nodeDidRelayout:(ASCellNode *)node
907907
[super endUpdates];
908908
}
909909

910+
#pragma mark - Memory Management
911+
912+
- (void)clearContents
913+
{
914+
for (NSArray *section in [_dataController completedNodes]) {
915+
for (ASDisplayNode *node in section) {
916+
[node recursivelyClearContents];
917+
}
918+
}
919+
}
920+
921+
- (void)clearFetchedData
922+
{
923+
for (NSArray *section in [_dataController completedNodes]) {
924+
for (ASDisplayNode *node in section) {
925+
[node recursivelyClearFetchedData];
926+
}
927+
}
928+
}
929+
910930
@end

AsyncDisplayKit/Details/UIView+ASConvenience.h

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,15 @@
4545
*/
4646
@protocol ASDisplayNodeViewProperties
4747

48-
@property (nonatomic, assign) BOOL clipsToBounds;
49-
@property (nonatomic, getter=isHidden) BOOL hidden;
50-
@property (nonatomic, assign) BOOL autoresizesSubviews;
51-
@property (nonatomic, assign) UIViewAutoresizing autoresizingMask;
52-
@property (nonatomic, retain) UIColor *tintColor;
53-
@property (nonatomic, assign) CGFloat alpha;
54-
@property (nonatomic, assign) CGRect bounds;
55-
@property (nonatomic, assign) UIViewContentMode contentMode;
48+
@property (nonatomic, assign) BOOL clipsToBounds;
49+
@property (nonatomic, getter=isHidden) BOOL hidden;
50+
@property (nonatomic, assign) BOOL autoresizesSubviews;
51+
@property (nonatomic, assign) UIViewAutoresizing autoresizingMask;
52+
@property (nonatomic, retain) UIColor *tintColor;
53+
@property (nonatomic, assign) CGFloat alpha;
54+
@property (nonatomic, assign) CGRect bounds;
55+
@property (nonatomic, assign) CGRect frame; // Only for use with nodes wrapping synchronous views
56+
@property (nonatomic, assign) UIViewContentMode contentMode;
5657
@property (nonatomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled;
5758
@property (nonatomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch;
5859
@property (nonatomic, assign, getter=asyncdisplaykit_isAsyncTransactionContainer, setter = asyncdisplaykit_setAsyncTransactionContainer:) BOOL asyncdisplaykit_asyncTransactionContainer;

AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,42 @@ - (void)setFrame:(CGRect)rect
178178
ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)");
179179
#endif
180180

181-
[self __setSafeFrame:rect];
181+
if (_flags.synchronous && !_flags.layerBacked) {
182+
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
183+
_setToViewOnly(frame, rect);
184+
} else {
185+
// This is by far the common case / hot path.
186+
[self __setSafeFrame:rect];
187+
}
188+
}
189+
190+
/**
191+
* Sets a new frame to this node by changing its bounds and position. This method can be safely called even if
192+
* the transform is a non-identity transform, because bounds and position can be set instead of frame.
193+
* This is NOT called for synchronous nodes (wrapping regular views), which may rely on a [UIView setFrame:] call.
194+
* A notable example of the latter is UITableView, which won't resize its internal container if only layer bounds are set.
195+
*/
196+
- (void)__setSafeFrame:(CGRect)rect
197+
{
198+
ASDisplayNodeAssertThreadAffinity(self);
199+
ASDN::MutexLocker l(_propertyLock);
200+
201+
BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain());
202+
203+
CGPoint origin = (useLayer ? _layer.bounds.origin : self.bounds.origin);
204+
CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint);
205+
206+
CGRect bounds = (CGRect){ origin, rect.size };
207+
CGPoint position = CGPointMake(rect.origin.x + rect.size.width * anchorPoint.x,
208+
rect.origin.y + rect.size.height * anchorPoint.y);
209+
210+
if (useLayer) {
211+
_layer.bounds = bounds;
212+
_layer.position = position;
213+
} else {
214+
self.bounds = bounds;
215+
self.position = position;
216+
}
182217
}
183218

184219
- (void)setNeedsDisplay

AsyncDisplayKit/Private/ASDisplayNodeInternal.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
125125
- (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize;
126126

127127
- (void)__setNeedsLayout;
128-
/**
129-
* Sets a new frame to this node by changing its bounds and position. This method can be safely called even if the transform property
130-
* contains a non-identity transform, because bounds and position can be changed in such case.
131-
*/
132-
- (void)__setSafeFrame:(CGRect)rect;
133128
- (void)__layout;
134129
- (void)__setSupernode:(ASDisplayNode *)supernode;
135130

0 commit comments

Comments
 (0)