diff --git a/CHANGELOG.md b/CHANGELOG.md index c4b306dff..b7ddb7aaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,4 @@ - [Yoga Beta] Improvements to the experimental support for Yoga layout [Scott Goodson](appleguy) - Update the rasterization API and un-deprecate it. [Adlai Holler](https://github.com/Adlai-Holler)[#82](https://github.com/TextureGroup/Texture/pull/49) - Simplified & optimized hashing code. [Adlai Holler](https://github.com/Adlai-Holler) [#86](https://github.com/TextureGroup/Texture/pull/86) +- Improve the performance & safety of ASDisplayNode subnodes. [Adlai Holler](https://github.com/Adlai-Holler) [#223](https://github.com/TextureGroup/Texture/pull/223) diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index 424ad2401..2d528c91f 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -121,7 +121,7 @@ extern NSInteger const ASDefaultDrawingPriority; * */ -@interface ASDisplayNode : NSObject +@interface ASDisplayNode : NSObject /** @name Initializing a node object */ diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 5eadb854c..13d1b5bc6 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -430,8 +430,6 @@ - (void)dealloc [self _scheduleIvarsForMainDeallocation]; } - _subnodes = nil; - #if YOGA if (_yogaNode != NULL) { YGNodeFree(_yogaNode); @@ -1411,17 +1409,15 @@ - (void)_layoutSublayouts ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); ASLayout *layout; - NSArray *subnodes; { ASDN::MutexLocker l(__instanceLock__); - if (_calculatedDisplayNodeLayout->isDirty() || _subnodes.count == 0) { + if (_calculatedDisplayNodeLayout->isDirty()) { return; } layout = _calculatedDisplayNodeLayout->layout; - subnodes = [_subnodes copy]; } - for (ASDisplayNode *node in subnodes) { + for (ASDisplayNode *node in self.subnodes) { CGRect frame = [layout frameForElement:node]; if (CGRectIsNull(frame)) { // There is no frame for this node in our layout. @@ -2675,7 +2671,12 @@ - (void)_setSupernode:(ASDisplayNode *)newSupernode - (NSArray *)subnodes { ASDN::MutexLocker l(__instanceLock__); - return ([_subnodes copy] ?: @[]); + if (_cachedSubnodes == nil) { + _cachedSubnodes = [_subnodes copy]; + } else { + ASDisplayNodeAssert(ASObjectIsEqual(_cachedSubnodes, _subnodes), @"Expected _subnodes and _cachedSubnodes to have the same contents."); + } + return _cachedSubnodes ?: @[]; } /* @@ -2736,6 +2737,7 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnod _subnodes = [[NSMutableArray alloc] init]; } [_subnodes insertObject:subnode atIndex:subnodeIndex]; + _cachedSubnodes = nil; __instanceLock__.unlock(); // This call will apply our .hierarchyState to the new subnode. @@ -3071,6 +3073,7 @@ - (void)_removeSubnode:(ASDisplayNode *)subnode __instanceLock__.lock(); [_subnodes removeObjectIdenticalTo:subnode]; + _cachedSubnodes = nil; __instanceLock__.unlock(); [subnode _setSupernode:nil]; @@ -4060,13 +4063,6 @@ - (CGRect)_frameInWindow } } -#pragma mark - NSFastEnumeration - -- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len -{ - return [self.subnodes countByEnumeratingWithState:state objects:buffer count:len]; -} - #pragma mark - ASPrimitiveTraitCollection - (ASPrimitiveTraitCollection)primitiveTraitCollection diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 7590d9511..964676b17 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -127,6 +127,9 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo ASDisplayNode * __weak _supernode; NSMutableArray *_subnodes; + // Set this to nil whenever you modify _subnodes + NSArray *_cachedSubnodes; + ASLayoutElementStyle *_style; ASPrimitiveTraitCollection _primitiveTraitCollection;