Optimizations for ASTextNode handling of renderer allocation and deallocation.

Optimizations for ASDisplayNode handling of bridged property "contentMode", especially for layer-backed nodes.
This commit is contained in:
Scott Goodson 2015-12-25 01:36:32 -08:00
parent 1ec1957b44
commit 99fbc97bda
6 changed files with 107 additions and 25 deletions

View File

@ -62,6 +62,15 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
- (void)dealloc - (void)dealloc
{ {
CGColorRelease(_backgroundColor); 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 @end
@ -157,6 +166,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
if (_shadowColor != NULL) { if (_shadowColor != NULL) {
CGColorRelease(_shadowColor); CGColorRelease(_shadowColor);
} }
[self _invalidateRenderer];
if (_longPressGestureRecognizer) { if (_longPressGestureRecognizer) {
_longPressGestureRecognizer.delegate = nil; _longPressGestureRecognizer.delegate = nil;
@ -189,6 +200,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
return [[self _renderer] size]; 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 - (void)displayDidFinish
{ {
[super displayDidFinish]; [super displayDidFinish];
@ -263,16 +276,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)_invalidateRenderer - (void)_invalidateRenderer
{ {
ASDN::MutexLocker l(_rendererLock); ASDN::MutexLocker l(_rendererLock);
if (_renderer) { if (_renderer) {
// Destruction of the layout managers/containers/text storage is quite // Destruction of the layout managers/containers/text storage is quite
// expensive, and can take some time, so we dispatch onto a bg queue to // expensive, and can take some time, so we dispatch onto a bg queue to
// actually dealloc. // actually dealloc.
__block ASTextKitRenderer *renderer = _renderer; __block ASTextKitRenderer *renderer = _renderer;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ ASPerformBlockOnBackgroundThread(^{
renderer = nil; renderer = nil;
}); });
_renderer = nil;
} }
_renderer = nil;
} }
- (void)_invalidateRendererIfNeeded - (void)_invalidateRendererIfNeeded
@ -320,7 +334,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Modifying User Text #pragma mark - Modifying User Text
- (void)setAttributedString:(NSAttributedString *)attributedString { - (void)setAttributedString:(NSAttributedString *)attributedString
{
if (ASObjectIsEqual(attributedString, _attributedString)) { if (ASObjectIsEqual(attributedString, _attributedString)) {
return; return;
} }

View File

@ -446,19 +446,27 @@
{ {
_bridge_prologue; _bridge_prologue;
if (__loaded) { if (__loaded) {
return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity); if (_flags.layerBacked) {
return ASDisplayNodeUIContentModeFromCAContentsGravity(_layer.contentsGravity);
} else {
return _view.contentMode;
}
} else { } else {
return self.pendingViewState.contentMode; return self.pendingViewState.contentMode;
} }
} }
- (void)setContentMode:(UIViewContentMode)mode - (void)setContentMode:(UIViewContentMode)contentMode
{ {
_bridge_prologue; _bridge_prologue;
if (__loaded) { if (__loaded) {
_layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(mode); if (_flags.layerBacked) {
_layer.contentsGravity = ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode);
} else {
_view.contentMode = contentMode;
}
} else { } else {
self.pendingViewState.contentMode = mode; self.pendingViewState.contentMode = contentMode;
} }
} }

View File

@ -18,6 +18,7 @@ ASDISPLAYNODE_EXTERN_C_BEGIN
BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector); BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector);
BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector); BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector);
void ASPerformBlockOnMainThread(void (^block)()); void ASPerformBlockOnMainThread(void (^block)());
void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORITY_DEFAULT
CGFloat ASScreenScale(); CGFloat ASScreenScale();

View File

@ -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() CGFloat ASScreenScale()
{ {
static CGFloat _scale; static CGFloat _scale;

View File

@ -119,11 +119,31 @@ NSString *const ASDisplayNodeCAContentsGravityFromUIContentMode(UIViewContentMod
return nil; return nil;
} }
#define ContentModeCacheSize 10
UIViewContentMode ASDisplayNodeUIContentModeFromCAContentsGravity(NSString *const contentsGravity) 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)) { 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;
} }
} }

View File

@ -32,7 +32,9 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
@implementation ASTextKitRenderer { @implementation ASTextKitRenderer {
CGSize _calculatedSize; CGSize _calculatedSize;
BOOL _sizeIsCalculated;
} }
@synthesize attributes = _attributes, context = _context, shadower = _shadower, truncater = _truncater;
#pragma mark - Initialization #pragma mark - Initialization
@ -42,30 +44,50 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
if (self = [super init]) { if (self = [super init]) {
_constrainedSize = constrainedSize; _constrainedSize = constrainedSize;
_attributes = attributes; _attributes = attributes;
_sizeIsCalculated = NO;
}
return self;
}
- (ASTextKitShadower *)shadower
{
if (!_shadower) {
ASTextKitAttributes attributes = _attributes;
_shadower = [[ASTextKitShadower alloc] initWithShadowOffset:attributes.shadowOffset _shadower = [[ASTextKitShadower alloc] initWithShadowOffset:attributes.shadowOffset
shadowColor:attributes.shadowColor shadowColor:attributes.shadowColor
shadowOpacity:attributes.shadowOpacity shadowOpacity:attributes.shadowOpacity
shadowRadius:attributes.shadowRadius]; shadowRadius:attributes.shadowRadius];
}
return _shadower;
}
- (ASTextKitTailTruncater *)truncater
{
if (!_truncater) {
ASTextKitAttributes attributes = _attributes;
// We must inset the constrained size by the size of the shadower. // 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 _context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString
lineBreakMode:attributes.lineBreakMode lineBreakMode:attributes.lineBreakMode
maximumNumberOfLines:attributes.maximumNumberOfLines maximumNumberOfLines:attributes.maximumNumberOfLines
exclusionPaths:attributes.exclusionPaths exclusionPaths:attributes.exclusionPaths
constrainedSize:shadowConstrainedSize constrainedSize:shadowConstrainedSize
layoutManagerFactory:attributes.layoutManagerFactory]; 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 #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 // Force glyph generation and layout, which may not have happened yet (and isn't triggered by
// -usedRectForTextContainer:). // -usedRectForTextContainer:).
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
[layoutManager ensureLayoutForTextContainer:textContainer]; [layoutManager ensureLayoutForTextContainer:textContainer];
}]; }];
CGRect constrainedRect = {CGPointZero, _constrainedSize}; CGRect constrainedRect = {CGPointZero, _constrainedSize};
__block CGRect boundingRect; __block CGRect boundingRect;
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { [[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
boundingRect = [layoutManager usedRectForTextContainer:textContainer]; boundingRect = [layoutManager usedRectForTextContainer:textContainer];
}]; }];
@ -94,6 +116,10 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
- (CGSize)size - (CGSize)size
{ {
if (!_sizeIsCalculated) {
[self _calculateSize];
_sizeIsCalculated = YES;
}
return _calculatedSize; 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 // 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."); ASDisplayNodeAssertNotNil(context, @"This is no good without a context.");
CGRect shadowInsetBounds = [_shadower insetRectWithConstrainedRect:bounds]; CGRect shadowInsetBounds = [[self shadower] insetRectWithConstrainedRect:bounds];
CGContextSaveGState(context); CGContextSaveGState(context);
[_shadower setShadowInContext:context]; [[self shadower] setShadowInContext:context];
UIGraphicsPushContext(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]; NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin]; [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
@ -125,7 +151,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
- (NSUInteger)lineCount - (NSUInteger)lineCount
{ {
__block NSUInteger lineCount = 0; __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++) { for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [layoutManager numberOfGlyphs]; lineCount++) {
[layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange]; [layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange];
} }
@ -135,7 +161,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
- (std::vector<NSRange>)visibleRanges - (std::vector<NSRange>)visibleRanges
{ {
return _truncater.visibleRanges; return [self truncater].visibleRanges;
} }
@end @end