diff --git a/CHANGELOG.md b/CHANGELOG.md index 96a8c1b2d3..9ec23015b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - Optimize display node accessibility by not creating attributed & non-attributed copies of hint, label, and value. [Adlai Holler](https://github.com/Adlai-Holler) - Add an experimental feature that reuses CTFramesetter objects in ASTextNode2 to improve performance. [Adlai Holler](https://github.com/Adlai-Holler) - Add NS_DESIGNATED_INITIALIZER to ASViewController initWithNode: [Michael Schneider](https://github.com/maicki) [#1054](https://github.com/TextureGroup/Texture/pull/1054) +- Optimize text stack by removing unneeded copying. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.7 - Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877) diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index 7d3d8d7b34..3ab1d2f35c 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -236,12 +236,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; ASLockScopeSelf(); - ASTextContainer *container = [_textContainer copy]; - NSAttributedString *attributedText = self.attributedText; - container.size = constrainedSize; + ASTextContainer *container; + if (!CGSizeEqualToSize(container.size, constrainedSize)) { + container = [_textContainer copy]; + container.size = constrainedSize; + [container makeImmutable]; + } else { + container = _textContainer; + } [self _ensureTruncationText]; - NSMutableAttributedString *mutableText = [attributedText mutableCopy]; + NSMutableAttributedString *mutableText = [_attributedText mutableCopy]; [self prepareAttributedString:mutableText]; ASTextLayout *layout = [ASTextNode2 compatibleLayoutWithContainer:container text:mutableText]; @@ -365,9 +370,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; { ASLockScopeSelf(); [self _ensureTruncationText]; + + // Unlike layout, here we must copy the container since drawing is asynchronous. ASTextContainer *copiedContainer = [_textContainer copy]; copiedContainer.size = self.bounds.size; - NSMutableAttributedString *mutableText = [self.attributedText mutableCopy] ?: [[NSMutableAttributedString alloc] init]; + [copiedContainer makeImmutable]; + NSMutableAttributedString *mutableText = [_attributedText mutableCopy] ?: [[NSMutableAttributedString alloc] init]; [self prepareAttributedString:mutableText]; diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.h b/Source/Private/TextExperiment/Component/ASTextLayout.h index 1a6625a3af..c78f4271f3 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.h +++ b/Source/Private/TextExperiment/Component/ASTextLayout.h @@ -67,6 +67,9 @@ AS_EXTERN const CGSize ASTextContainerMaxSize; /// Creates a container with the specified path. @param path The path. + (instancetype)containerWithPath:(nullable UIBezierPath *)path NS_RETURNS_RETAINED; +/// Mark this immutable, so you get free copies going forward. +- (void)makeImmutable; + /// The constrained size. (if the size is larger than ASTextContainerMaxSize, it will be clipped) @property CGSize size; diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.m b/Source/Private/TextExperiment/Component/ASTextLayout.m index d3d316d14d..4b3ba691f8 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.m +++ b/Source/Private/TextExperiment/Component/ASTextLayout.m @@ -17,6 +17,7 @@ #import +#import #import #import #import @@ -134,26 +135,36 @@ static CGColorRef ASTextGetCGColor(CGColorRef color) { return self; } -- (id)copyWithZone:(NSZone *)zone { - ASTextContainer *one = [self.class new]; +- (id)copyForced:(BOOL)forceCopy +{ dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); + if (_readonly && !forceCopy) { + dispatch_semaphore_signal(_lock); + return self; + } + + ASTextContainer *one = [self.class new]; one->_size = _size; one->_insets = _insets; one->_path = _path; - one->_exclusionPaths = _exclusionPaths.copy; + one->_exclusionPaths = [_exclusionPaths copy]; one->_pathFillEvenOdd = _pathFillEvenOdd; one->_pathLineWidth = _pathLineWidth; one->_verticalForm = _verticalForm; one->_maximumNumberOfRows = _maximumNumberOfRows; one->_truncationType = _truncationType; - one->_truncationToken = _truncationToken.copy; + one->_truncationToken = [_truncationToken copy]; one->_linePositionModifier = [(NSObject *)_linePositionModifier copy]; dispatch_semaphore_signal(_lock); return one; } -- (id)mutableCopyWithZone:(nullable NSZone *)zone { - return [self copyWithZone:zone]; +- (id)copyWithZone:(NSZone *)zone { + return [self copyForced:NO]; +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + return [self copyForced:YES]; } - (void)encodeWithCoder:(NSCoder *)aCoder { @@ -189,18 +200,25 @@ static CGColorRef ASTextGetCGColor(CGColorRef color) { return self; } +- (void)makeImmutable +{ + dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); + _readonly = YES; + dispatch_semaphore_signal(_lock); +} + #define Getter(...) \ dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ __VA_ARGS__; \ dispatch_semaphore_signal(_lock); #define Setter(...) \ -if (_readonly) { \ -@throw [NSException exceptionWithName:NSInternalInconsistencyException \ -reason:@"Cannot change the property of the 'container' in 'ASTextLayout'." userInfo:nil]; \ -return; \ -} \ dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ +if (__builtin_expect(_readonly, NO)) { \ + ASDisplayNodeFailAssert(@"Attempt to modify immutable text container."); \ + dispatch_semaphore_signal(_lock); \ + return; \ +} \ __VA_ARGS__; \ dispatch_semaphore_signal(_lock); @@ -407,11 +425,10 @@ dispatch_semaphore_signal(_lock); if (lineRowsIndex) free(lineRowsIndex); \ return nil; } - text = text.mutableCopy; - container = container.copy; + container = [container copy]; if (!text || !container) return nil; if (range.location + range.length > text.length) return nil; - container->_readonly = YES; + [container makeImmutable]; maximumNumberOfRows = container.maximumNumberOfRows; // It may use larger constraint size when create CTFrame with