Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit d5a7c19

Browse files
maickiappleguy
authored andcommitted
[ASLayoutSpec] Use childrenMap directly to prevent creating an NSArray within ASDK Part 2 (#2021)
* Revert "Revert "[ASLayoutSpec] Use childrenMap directly to prevent creating an NSArray within ASDK (#1937)"" This reverts commit 735b4eb. * Fix crash and add exception for mutating while using fast enumeration of ASLayoutSpec children NSFastEnumeration is potentially quite dangerous in the wrong hands. In particular, it does not provide a safe mechanism for you to return temporary objects directly, and it does not provide any guarantee that you will be called when the enumeration has completed; therefore if we generate temporaries and store them in an instance variable, we will not necessarily be able to clean them up! This means fast enumeration methods should never be called within an autorelease pool or the autorelease pool be drained within the fast enumeration loop. The reason is we store references to objects in the stackBuf struct by casting the child pointer to __autoreleasing id. If we pop the autorelease pool between calls to -countByEnumeratingWithState:objects:count:, it will die in a messy explosion of pointer dereferences and EXC_BAD_ACCESS. * Add tests for ASDisplayNode and ASLayoutSpec fast enumeration
1 parent 3a1a987 commit d5a7c19

11 files changed

Lines changed: 180 additions & 52 deletions

File tree

AsyncDisplayKit.xcodeproj/project.pbxproj

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@
214214
69E1006F1CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; };
215215
69E100701CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; };
216216
69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; };
217+
69F6058D1D3DA27E00C8CA38 /* ASLayoutSpec+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F6058C1D3DA27E00C8CA38 /* ASLayoutSpec+Private.h */; };
217218
7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
218219
764D83D51C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; settings = {ATTRIBUTES = (Public, ); }; };
219220
764D83D61C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */; };
@@ -965,6 +966,7 @@
965966
69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironmentInternal.h; sourceTree = "<group>"; };
966967
69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEnvironmentInternal.mm; sourceTree = "<group>"; };
967968
69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = "<group>"; };
969+
69F6058C1D3DA27E00C8CA38 /* ASLayoutSpec+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASLayoutSpec+Private.h"; sourceTree = "<group>"; };
968970
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
969971
764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit+Debug.h"; sourceTree = "<group>"; };
970972
764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AsyncDisplayKit+Debug.m"; sourceTree = "<group>"; };
@@ -1470,6 +1472,8 @@
14701472
044285051BAA63FE00D16268 /* ASBatchFetching.h */,
14711473
044285061BAA63FE00D16268 /* ASBatchFetching.m */,
14721474
251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */,
1475+
8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */,
1476+
8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */,
14731477
AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */,
14741478
AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */,
14751479
058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */,
@@ -1478,16 +1482,17 @@
14781482
DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */,
14791483
058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */,
14801484
058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */,
1481-
E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */,
1482-
E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */,
14831485
69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */,
14841486
69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */,
1487+
68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */,
14851488
058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */,
14861489
058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */,
1487-
68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */,
14881490
ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */,
14891491
ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */,
1492+
69F6058C1D3DA27E00C8CA38 /* ASLayoutSpec+Private.h */,
14901493
ACF6ED451B17847A00DA7C62 /* ASLayoutSpecUtilities.h */,
1494+
E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */,
1495+
E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */,
14911496
0442850B1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h */,
14921497
0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */,
14931498
CC3B20811C3F76D600798563 /* ASPendingStateController.h */,
@@ -1505,8 +1510,6 @@
15051510
CC3B20881C3F7A5400798563 /* ASWeakSet.m */,
15061511
DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */,
15071512
DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */,
1508-
8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */,
1509-
8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */,
15101513
);
15111514
path = Private;
15121515
sourceTree = "<group>";
@@ -1740,6 +1743,7 @@
17401743
B350625B1B010F070018CF92 /* ASEqualityHelpers.h in Headers */,
17411744
680346941CE4052A0009FEB4 /* ASNavigationController.h in Headers */,
17421745
B350621B1B010EFD0018CF92 /* ASFlowLayoutController.h in Headers */,
1746+
69F6058D1D3DA27E00C8CA38 /* ASLayoutSpec+Private.h in Headers */,
17431747
B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */,
17441748
C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */,
17451749
AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */,

AsyncDisplayKit/ASDisplayNode.mm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2981,6 +2981,15 @@ - (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)environmentT
29812981
}
29822982
}
29832983

2984+
#pragma mark - NSFastEnumeration
2985+
2986+
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
2987+
objects:(id __unsafe_unretained [])stackbuf
2988+
count:(NSUInteger)stackbufLength
2989+
{
2990+
return [self.children countByEnumeratingWithState:state objects:stackbuf count:stackbufLength];
2991+
}
2992+
29842993
ASEnvironmentLayoutOptionsForwarding
29852994
ASEnvironmentLayoutExtensibilityForwarding
29862995

AsyncDisplayKit/ASViewController.mm

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,7 @@ - (void)progagateNewEnvironmentTraitCollection:(ASEnvironmentTraitCollection)env
289289
self.node.environmentState = environmentState;
290290
[self.node setNeedsLayout];
291291

292-
NSArray<id<ASEnvironment>> *children = [self.node children];
293-
for (id<ASEnvironment> child in children) {
292+
for (id<ASEnvironment> child in self.node) {
294293
ASEnvironmentStatePropagateDown(child, environmentState.environmentTraitCollection);
295294
}
296295
}

AsyncDisplayKit/Details/ASEnvironment.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ ASDISPLAYNODE_EXTERN_C_END
9999
* defined in an ASEnvironmentState up and down the ASEnvironment tree. To be able to define how merges of
100100
* States should happen, specific merge functions can be provided
101101
*/
102-
@protocol ASEnvironment <NSObject>
102+
@protocol ASEnvironment <NSObject, NSFastEnumeration>
103103

104104
/// The environment collection of an object which class conforms to the ASEnvironment protocol
105105
- (ASEnvironmentState)environmentState;
@@ -126,6 +126,7 @@ ASDISPLAYNODE_EXTERN_C_END
126126

127127
/// sets a trait collection on this environment state.
128128
- (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)environmentTraitCollection;
129+
129130
@end
130131

131132
// ASCollection/TableNodes don't actually have ASCellNodes as subnodes. Because of this we can't rely on display trait

AsyncDisplayKit/Layout/ASLayoutSpec.mm

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// of patent rights can be found in the PATENTS file in the same directory.
99
//
1010

11-
#import "ASLayoutSpec.h"
11+
#import "ASLayoutSpec+Private.h"
1212

1313
#import "ASAssert.h"
1414
#import "ASEnvironmentInternal.h"
@@ -17,16 +17,13 @@
1717
#import "ASThread.h"
1818
#import "ASTraitCollection.h"
1919

20-
#import <objc/runtime.h>
21-
#import <map>
2220
#import <vector>
2321

24-
typedef std::map<unsigned long, id<ASLayoutable>, std::less<unsigned long>> ASChildMap;
25-
2622
@interface ASLayoutSpec() {
2723
ASEnvironmentState _environmentState;
2824
ASDN::RecursiveMutex __instanceLock__;
29-
ASChildMap _children;
25+
ASChildrenMap _childrenMap;
26+
unsigned long _mutations;
3027
}
3128
@end
3229

@@ -35,6 +32,7 @@ @implementation ASLayoutSpec
3532
// these dynamic properties all defined in ASLayoutOptionsPrivate.m
3633
@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis,
3734
alignSelf, ascender, descender, sizeRange, layoutPosition, layoutableType;
35+
@synthesize parent = _parent;
3836
@synthesize isFinalLayoutable = _isFinalLayoutable;
3937

4038
- (instancetype)init
@@ -44,6 +42,7 @@ - (instancetype)init
4442
}
4543
_isMutable = YES;
4644
_environmentState = ASEnvironmentStateMakeDefault();
45+
_mutations = 0;
4746
return self;
4847
}
4948

@@ -105,6 +104,8 @@ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
105104
return child;
106105
}
107106

107+
#pragma mark - Parent
108+
108109
- (void)setParent:(id<ASLayoutable>)parent
109110
{
110111
// FIXME: Locking should be evaluated here. _parent is not widely used yet, though.
@@ -115,30 +116,33 @@ - (void)setParent:(id<ASLayoutable>)parent
115116
}
116117
}
117118

119+
- (id<ASLayoutable>)parent
120+
{
121+
return _parent;
122+
}
123+
124+
#pragma mark - Children
125+
118126
- (void)setChild:(id<ASLayoutable>)child
119127
{
120-
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
121-
if (child) {
122-
id<ASLayoutable> finalLayoutable = [self layoutableToAddFromLayoutable:child];
123-
if (finalLayoutable) {
124-
_children[0] = finalLayoutable;
125-
[self propagateUpLayoutable:finalLayoutable];
126-
}
127-
} else {
128-
_children.erase(0);
129-
}
128+
[self setChild:child forIndex:0];
130129
}
131130

132131
- (void)setChild:(id<ASLayoutable>)child forIndex:(NSUInteger)index
133132
{
134133
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
135134
if (child) {
136135
id<ASLayoutable> finalLayoutable = [self layoutableToAddFromLayoutable:child];
137-
_children[index] = finalLayoutable;
136+
if (finalLayoutable) {
137+
_childrenMap[index] = finalLayoutable;
138+
[self propagateUpLayoutable:finalLayoutable];
139+
}
138140
} else {
139-
_children.erase(index);
141+
_childrenMap.erase(index);
140142
}
141-
// TODO: Should we propagate up the layoutable at it could happen that multiple children will propagated up their
143+
_mutations++;
144+
145+
// TODO: Should we propagate up the layoutable as it could happen that multiple children will propagated up their
142146
// layout options and one child will overwrite values from another child
143147
// [self propagateUpLayoutable:finalLayoutable];
144148
}
@@ -147,37 +151,73 @@ - (void)setChildren:(NSArray<id<ASLayoutable>> *)children
147151
{
148152
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
149153

150-
_children.clear();
154+
_childrenMap.clear();
151155
NSUInteger i = 0;
152156
for (id<ASLayoutable> child in children) {
153-
_children[i] = [self layoutableToAddFromLayoutable:child];
157+
_childrenMap[i] = [self layoutableToAddFromLayoutable:child];
154158
i += 1;
159+
160+
_mutations++;
155161
}
156162
}
157163

158164
- (id<ASLayoutable>)childForIndex:(NSUInteger)index
159165
{
160-
if (index < _children.size()) {
161-
return _children[index];
166+
if (index < _childrenMap.size()) {
167+
return _childrenMap[index];
162168
}
163169
return nil;
164170
}
165171

166172
- (id<ASLayoutable>)child
167173
{
168-
return _children[0];
174+
return _childrenMap[0];
169175
}
170176

171177
- (NSArray *)children
172178
{
179+
// If used inside ASDK, the childrenMap property should be preferred over the children array to prevent
180+
// unecessary boxing
173181
std::vector<ASLayout *> children;
174-
for (ASChildMap::iterator it = _children.begin(); it != _children.end(); ++it ) {
175-
children.push_back(it->second);
182+
for (auto const &entry : _childrenMap) {
183+
children.push_back(entry.second);
176184
}
177-
185+
178186
return [NSArray arrayWithObjects:&children[0] count:children.size()];
179187
}
180188

189+
#pragma mark - NSFastEnumeration
190+
191+
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
192+
objects:(id __unsafe_unretained [])stackbuf
193+
count:(NSUInteger)stackbufLength
194+
{
195+
NSUInteger count = 0;
196+
unsigned long countOfItemsAlreadyEnumerated = state->state;
197+
198+
if (countOfItemsAlreadyEnumerated == 0) {
199+
state->mutationsPtr = &_mutations;
200+
}
201+
202+
if (countOfItemsAlreadyEnumerated < _childrenMap.size()) {
203+
state->itemsPtr = stackbuf;
204+
205+
while((countOfItemsAlreadyEnumerated < _childrenMap.size()) && (count < stackbufLength)) {
206+
// Hold on for the object while enumerating
207+
__autoreleasing id child = _childrenMap[countOfItemsAlreadyEnumerated];
208+
stackbuf[count] = child;
209+
countOfItemsAlreadyEnumerated++;
210+
count++;
211+
}
212+
} else {
213+
count = 0;
214+
}
215+
216+
state->state = countOfItemsAlreadyEnumerated;
217+
218+
return count;
219+
}
220+
181221
#pragma mark - ASEnvironment
182222

183223
- (ASEnvironmentState)environmentState
@@ -233,6 +273,15 @@ - (ASTraitCollection *)asyncTraitCollection
233273

234274
@end
235275

276+
@implementation ASLayoutSpec (Private)
277+
278+
- (ASChildrenMap)childrenMap
279+
{
280+
return _childrenMap;
281+
}
282+
283+
@end
284+
236285
@implementation ASLayoutSpec (Debugging)
237286

238287
#pragma mark - ASLayoutableAsciiArtProtocol

AsyncDisplayKit/Layout/ASStackLayoutSpec.mm

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#import "ASInternalHelpers.h"
1515

16+
#import "ASLayoutSpec+Private.h"
1617
#import "ASLayoutSpecUtilities.h"
1718
#import "ASStackBaselinePositionedLayout.h"
1819
#import "ASThread.h"
@@ -58,7 +59,7 @@ - (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGF
5859
_alignItems = alignItems;
5960
_justifyContent = justifyContent;
6061

61-
[self setChildren:children];
62+
self.children = children;
6263
return self;
6364
}
6465

@@ -120,7 +121,9 @@ - (void)setBaselineRelativeArrangement:(BOOL)baselineRelativeArrangement
120121

121122
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
122123
{
123-
if (self.children.count == 0) {
124+
auto const &children = self.childrenMap;
125+
126+
if (children.size() == 0) {
124127
return [ASLayout layoutWithLayoutableObject:self
125128
constrainedSizeRange:constrainedSize
126129
size:constrainedSize.min];
@@ -129,9 +132,9 @@ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
129132
ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .baselineRelativeArrangement = _baselineRelativeArrangement};
130133
BOOL needsBaselinePass = _baselineRelativeArrangement || _alignItems == ASStackLayoutAlignItemsBaselineFirst || _alignItems == ASStackLayoutAlignItemsBaselineLast;
131134

132-
std::vector<id<ASLayoutable>> stackChildren = std::vector<id<ASLayoutable>>();
133-
for (id<ASLayoutable> child in self.children) {
134-
stackChildren.push_back(child);
135+
std::vector<id<ASLayoutable>> stackChildren;
136+
for (auto const &entry : children) {
137+
stackChildren.push_back(entry.second);
135138
}
136139

137140
const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize);
@@ -143,14 +146,18 @@ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
143146
// regardless of whether or not this stack aligns to baseline, we should let ASStackBaselinePositionedLayout::compute find the max ascender
144147
// and min descender in case this spec is a child in another spec that wants to align to a baseline.
145148
const auto baselinePositionedLayout = ASStackBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize);
146-
if (self.direction == ASStackLayoutDirectionVertical) {
147-
ASDN::MutexLocker l(__instanceLock__);
148-
self.ascender = [[self.children firstObject] ascender];
149-
self.descender = [[self.children lastObject] descender];
150-
} else {
149+
{
151150
ASDN::MutexLocker l(__instanceLock__);
152-
self.ascender = baselinePositionedLayout.ascender;
153-
self.descender = baselinePositionedLayout.descender;
151+
if (self.direction == ASStackLayoutDirectionVertical) {
152+
if (stackChildren.size() > 0) {
153+
self.ascender = stackChildren.front().ascender;
154+
self.descender = stackChildren.back().descender;
155+
}
156+
157+
} else {
158+
self.ascender = baselinePositionedLayout.ascender;
159+
self.descender = baselinePositionedLayout.descender;
160+
}
154161
}
155162

156163
if (needsBaselinePass) {

AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#import "ASStaticLayoutSpec.h"
1212

13+
#import "ASLayoutSpec+Private.h"
1314
#import "ASLayoutSpecUtilities.h"
1415
#import "ASLayout.h"
1516

@@ -38,10 +39,8 @@ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
3839
{
3940
CGSize maxConstrainedSize = CGSizeMake(constrainedSize.max.width, constrainedSize.max.height);
4041

41-
NSArray *children = self.children;
42-
NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:children.count];
43-
44-
for (id<ASLayoutable> child in children) {
42+
NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:self.childrenMap.size()];
43+
for (id<ASLayoutable> child in self) {
4544
CGPoint layoutPosition = child.layoutPosition;
4645
CGSize autoMaxSize = CGSizeMake(maxConstrainedSize.width - layoutPosition.x,
4746
maxConstrainedSize.height - layoutPosition.y);

0 commit comments

Comments
 (0)