mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-16 17:00:58 +00:00
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:
parent
1ec1957b44
commit
99fbc97bda
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user