From 99fbc97bdaecd2f99009b9734ba0f8f2b665632b Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Fri, 25 Dec 2015 01:36:32 -0800 Subject: [PATCH] Optimizations for ASTextNode handling of renderer allocation and deallocation. Optimizations for ASDisplayNode handling of bridged property "contentMode", especially for layer-backed nodes. --- AsyncDisplayKit/ASTextNode.mm | 21 ++++++- .../Private/ASDisplayNode+UIViewBridge.mm | 16 +++-- AsyncDisplayKit/Private/ASInternalHelpers.h | 1 + AsyncDisplayKit/Private/ASInternalHelpers.mm | 12 ++++ .../Private/_ASCoreAnimationExtras.mm | 24 +++++++- AsyncDisplayKit/TextKit/ASTextKitRenderer.mm | 58 ++++++++++++++----- 6 files changed, 107 insertions(+), 25 deletions(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 3eb5c5ba..fdcfc26b 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -62,6 +62,15 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (void)dealloc { CGColorRelease(_backgroundColor); + + // Destruction of the layout managers/containers/text storage is quite + // expensive, and can take some time, so we dispatch onto a bg queue to + // actually dealloc. + __block ASTextKitRenderer *renderer = _renderer; + ASPerformBlockOnBackgroundThread(^{ + renderer = nil; + }); + _renderer = nil; } @end @@ -157,6 +166,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; if (_shadowColor != NULL) { CGColorRelease(_shadowColor); } + + [self _invalidateRenderer]; if (_longPressGestureRecognizer) { _longPressGestureRecognizer.delegate = nil; @@ -189,6 +200,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; return [[self _renderer] size]; } +// FIXME: Re-evaluate if it is still the right decision to clear the renderer at this stage. +// This code was written before TextKit and when 512MB devices were still the overwhelming majority. - (void)displayDidFinish { [super displayDidFinish]; @@ -263,16 +276,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; - (void)_invalidateRenderer { ASDN::MutexLocker l(_rendererLock); + if (_renderer) { // Destruction of the layout managers/containers/text storage is quite // expensive, and can take some time, so we dispatch onto a bg queue to // actually dealloc. __block ASTextKitRenderer *renderer = _renderer; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + ASPerformBlockOnBackgroundThread(^{ renderer = nil; }); + _renderer = nil; } - _renderer = nil; } - (void)_invalidateRendererIfNeeded @@ -320,7 +334,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; #pragma mark - Modifying User Text -- (void)setAttributedString:(NSAttributedString *)attributedString { +- (void)setAttributedString:(NSAttributedString *)attributedString +{ if (ASObjectIsEqual(attributedString, _attributedString)) { return; } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 2239cc03..d14c042c 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -446,19 +446,27 @@ { _bridge_prologue; if (__loaded) { - return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity); + if (_flags.layerBacked) { + return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity); + } else { + return _view.contentMode; + } } else { return self.pendingViewState.contentMode; } } -- (void)setContentMode:(UIViewContentMode)mode +- (void)setContentMode:(UIViewContentMode)contentMode { _bridge_prologue; if (__loaded) { - _layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(mode); + if (_flags.layerBacked) { + _layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode); + } else { + _view.contentMode = contentMode; + } } else { - self.pendingViewState.contentMode = mode; + self.pendingViewState.contentMode = contentMode; } } diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 4d1e2ddc..1f173bd9 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -18,6 +18,7 @@ ASDISPLAYNODE_EXTERN_C_BEGIN BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector); BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector); void ASPerformBlockOnMainThread(void (^block)()); +void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORITY_DEFAULT CGFloat ASScreenScale(); diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.mm b/AsyncDisplayKit/Private/ASInternalHelpers.mm index 89a0e817..71ac09d2 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.mm +++ b/AsyncDisplayKit/Private/ASInternalHelpers.mm @@ -57,6 +57,18 @@ void ASPerformBlockOnMainThread(void (^block)()) } } +void ASPerformBlockOnBackgroundThread(void (^block)()) +{ + if ([NSThread isMainThread]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + block(); + }); + } else { + block(); + } +} + + CGFloat ASScreenScale() { static CGFloat _scale; diff --git a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm index a6205e1d..a0c3b14e 100644 --- a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm +++ b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm @@ -119,11 +119,31 @@ NSString *const ASDisplayNodeCAContentsGravityFromUIContentMode(UIViewContentMod return nil; } +#define ContentModeCacheSize 10 UIViewContentMode ASDisplayNodeUIContentModeFromCAContentsGravity(NSString *const contentsGravity) { - for (int i=0; i < ARRAY_COUNT(UIContentModeCAGravityLUT); i++) { + static int currentCacheIndex = 0; + static NSMutableArray *cachedStrings = [NSMutableArray arrayWithCapacity:ContentModeCacheSize]; + static UIViewContentMode cachedModes[ContentModeCacheSize] = {}; + + NSInteger foundCacheIndex = [cachedStrings indexOfObjectIdenticalTo:contentsGravity]; + if (foundCacheIndex != NSNotFound && foundCacheIndex < ContentModeCacheSize) { + return cachedModes[foundCacheIndex]; + } + + for (int i = 0; i < ARRAY_COUNT(UIContentModeCAGravityLUT); i++) { if (ASObjectIsEqual(UIContentModeCAGravityLUT[i].string, contentsGravity)) { - return UIContentModeCAGravityLUT[i].contentMode; + UIViewContentMode foundContentMode = UIContentModeCAGravityLUT[i].contentMode; + + if (currentCacheIndex < ContentModeCacheSize) { + // Cache the input value. This is almost always a different pointer than in our LUT and will frequently + // be the same value for an overwhelming majority of inputs. + [cachedStrings addObject:contentsGravity]; + cachedModes[currentCacheIndex] = foundContentMode; + currentCacheIndex++; + } + + return foundContentMode; } } diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm index c00d0061..7630c105 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer.mm @@ -32,7 +32,9 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() @implementation ASTextKitRenderer { CGSize _calculatedSize; + BOOL _sizeIsCalculated; } +@synthesize attributes = _attributes, context = _context, shadower = _shadower, truncater = _truncater; #pragma mark - Initialization @@ -42,30 +44,50 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() if (self = [super init]) { _constrainedSize = constrainedSize; _attributes = attributes; + _sizeIsCalculated = NO; + } + return self; +} +- (ASTextKitShadower *)shadower +{ + if (!_shadower) { + ASTextKitAttributes attributes = _attributes; _shadower = [[ASTextKitShadower alloc] initWithShadowOffset:attributes.shadowOffset shadowColor:attributes.shadowColor shadowOpacity:attributes.shadowOpacity shadowRadius:attributes.shadowRadius]; + } + return _shadower; +} +- (ASTextKitTailTruncater *)truncater +{ + if (!_truncater) { + ASTextKitAttributes attributes = _attributes; // We must inset the constrained size by the size of the shadower. - CGSize shadowConstrainedSize = [_shadower insetSizeWithConstrainedSize:_constrainedSize]; + CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; + _truncater = [[ASTextKitTailTruncater alloc] initWithContext:[self context] + truncationAttributedString:attributes.truncationAttributedString + avoidTailTruncationSet:attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet() + constrainedSize:shadowConstrainedSize]; + } + return _truncater; +} +- (ASTextKitContext *)context +{ + if (!_context) { + ASTextKitAttributes attributes = _attributes; + CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize]; _context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString lineBreakMode:attributes.lineBreakMode maximumNumberOfLines:attributes.maximumNumberOfLines exclusionPaths:attributes.exclusionPaths constrainedSize:shadowConstrainedSize layoutManagerFactory:attributes.layoutManagerFactory]; - - _truncater = [[ASTextKitTailTruncater alloc] initWithContext:_context - truncationAttributedString:attributes.truncationAttributedString - avoidTailTruncationSet:attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet() - constrainedSize:shadowConstrainedSize]; - - [self _calculateSize]; } - return self; + return _context; } #pragma mark - Sizing @@ -74,14 +96,14 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() { // Force glyph generation and layout, which may not have happened yet (and isn't triggered by // -usedRectForTextContainer:). - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { [layoutManager ensureLayoutForTextContainer:textContainer]; }]; CGRect constrainedRect = {CGPointZero, _constrainedSize}; __block CGRect boundingRect; - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { boundingRect = [layoutManager usedRectForTextContainer:textContainer]; }]; @@ -94,6 +116,10 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() - (CGSize)size { + if (!_sizeIsCalculated) { + [self _calculateSize]; + _sizeIsCalculated = YES; + } return _calculatedSize; } @@ -104,13 +130,13 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() // We add an assertion so we can track the rare conditions where a graphics context is not present ASDisplayNodeAssertNotNil(context, @"This is no good without a context."); - CGRect shadowInsetBounds = [_shadower insetRectWithConstrainedRect:bounds]; + CGRect shadowInsetBounds = [[self shadower] insetRectWithConstrainedRect:bounds]; CGContextSaveGState(context); - [_shadower setShadowInContext:context]; + [[self shadower] setShadowInContext:context]; UIGraphicsPushContext(context); - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; @@ -125,7 +151,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() - (NSUInteger)lineCount { __block NSUInteger lineCount = 0; - [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { + [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [layoutManager numberOfGlyphs]; lineCount++) { [layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; } @@ -135,7 +161,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet() - (std::vector)visibleRanges { - return _truncater.visibleRanges; + return [self truncater].visibleRanges; } @end