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
{
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;
}

View File

@ -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;
}
}

View File

@ -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();

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

View File

@ -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;
}
}

View File

@ -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<NSRange>)visibleRanges
{
return _truncater.visibleRanges;
return [self truncater].visibleRanges;
}
@end