-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix crash setting attributed text on multiple threads #1141
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -440,39 +440,46 @@ - (void)setAttributedText:(NSAttributedString *)attributedText | |
if (attributedText == nil) { | ||
attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil]; | ||
} | ||
|
||
// Don't hold textLock for too long. | ||
|
||
{ | ||
ASLockScopeSelf(); | ||
if (ASObjectIsEqual(attributedText, _attributedText)) { | ||
return; | ||
} | ||
|
||
_attributedText = ASCleanseAttributedStringOfCoreTextAttributes(attributedText); | ||
#if AS_TEXTNODE_RECORD_ATTRIBUTED_STRINGS | ||
[ASTextNode _registerAttributedText:_attributedText]; | ||
#endif | ||
NSAttributedString *cleanedAttributedString = ASCleanseAttributedStringOfCoreTextAttributes(attributedText); | ||
|
||
// Invalidating the truncation text must be done while we still hold the lock. Because after we release it, | ||
// another thread may set a new truncation text that will then be cleared by this thread, other may draw | ||
// this soon-to-be-invalidated text. | ||
[self _locked_invalidateTruncationText]; | ||
|
||
NSUInteger length = cleanedAttributedString.length; | ||
if (length > 0) { | ||
// Updating ascender and descender in one transaction while holding the lock. | ||
ASLayoutElementStyle *style = [self _locked_style]; | ||
style.ascender = [[self class] ascenderWithAttributedString:cleanedAttributedString]; | ||
style.descender = [[attributedText attribute:NSFontAttributeName atIndex:cleanedAttributedString.length - 1 effectiveRange:NULL] descender]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same deal with |
||
} | ||
|
||
// Update attributed text with cleaned attributed string | ||
_attributedText = cleanedAttributedString; | ||
} | ||
|
||
// Since truncation text matches style of attributedText, invalidate it now. | ||
[self _invalidateTruncationText]; | ||
|
||
NSUInteger length = _attributedText.length; | ||
if (length > 0) { | ||
self.style.ascender = [[self class] ascenderWithAttributedString:_attributedText]; | ||
self.style.descender = [[_attributedText attribute:NSFontAttributeName atIndex:length - 1 effectiveRange:NULL] descender]; | ||
} | ||
|
||
// Tell the display node superclasses that the cached layout is incorrect now | ||
[self setNeedsLayout]; | ||
|
||
// Force display to create renderer with new size and redisplay with new string | ||
[self setNeedsDisplay]; | ||
|
||
|
||
// Accessiblity | ||
self.accessibilityLabel = _attributedText.string; | ||
self.isAccessibilityElement = (length != 0); // We're an accessibility element by default if there is a string. | ||
let currentAttributedText = self.attributedText; // Grab attributed string again in case it changed in the meantime | ||
self.accessibilityLabel = currentAttributedText.string; | ||
self.isAccessibilityElement = (currentAttributedText.length != 0); // We're an accessibility element by default if there is a string. | ||
|
||
#if AS_TEXTNODE_RECORD_ATTRIBUTED_STRINGS | ||
[ASTextNode _registerAttributedText:_attributedText]; | ||
#endif | ||
} | ||
|
||
#pragma mark - Text Layout | ||
|
@@ -1166,13 +1173,15 @@ - (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedTe | |
{ | ||
if (ASLockedSelfCompareAssignCopy(_truncationAttributedText, truncationAttributedText)) { | ||
[self _invalidateTruncationText]; | ||
[self setNeedsDisplay]; | ||
} | ||
} | ||
|
||
- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage | ||
{ | ||
if (ASLockedSelfCompareAssignCopy(_additionalTruncationMessage, additionalTruncationMessage)) { | ||
[self _invalidateTruncationText]; | ||
[self setNeedsDisplay]; | ||
} | ||
} | ||
|
||
|
@@ -1231,12 +1240,13 @@ - (NSUInteger)lineCount | |
|
||
- (void)_invalidateTruncationText | ||
{ | ||
{ | ||
ASLockScopeSelf(); | ||
_composedTruncationText = nil; | ||
} | ||
ASLockScopeSelf(); | ||
[self _locked_invalidateTruncationText]; | ||
} | ||
|
||
[self setNeedsDisplay]; | ||
- (void)_locked_invalidateTruncationText | ||
{ | ||
_composedTruncationText = nil; | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -280,12 +280,13 @@ - (void)setAttributedText:(NSAttributedString *)attributedText | |
} | ||
|
||
// Since truncation text matches style of attributedText, invalidate it now. | ||
[self _invalidateTruncationText]; | ||
[self _locked_invalidateTruncationText]; | ||
|
||
NSUInteger length = attributedText.length; | ||
if (length > 0) { | ||
self.style.ascender = [[self class] ascenderWithAttributedString:attributedText]; | ||
self.style.descender = [[attributedText attribute:NSFontAttributeName atIndex:attributedText.length - 1 effectiveRange:NULL] descender]; | ||
ASLayoutElementStyle *style = [self _locked_style]; | ||
style.ascender = [[self class] ascenderWithAttributedString:attributedText]; | ||
style.descender = [[attributedText attribute:NSFontAttributeName atIndex:attributedText.length - 1 effectiveRange:NULL] descender]; | ||
} | ||
|
||
// Tell the display node superclasses that the cached layout is incorrect now | ||
|
@@ -1061,10 +1062,15 @@ - (NSUInteger)lineCount | |
- (void)_invalidateTruncationText | ||
{ | ||
ASLockScopeSelf(); | ||
_textContainer.truncationToken = nil; | ||
[self _locked_invalidateTruncationText]; | ||
[self setNeedsDisplay]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it actually ok to call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I asked that myself too, but I didn't want to change it within this diff. In theory the code in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
} | ||
|
||
- (void)_locked_invalidateTruncationText | ||
{ | ||
_textContainer.truncationToken = nil; | ||
} | ||
|
||
/** | ||
* @return the additional truncation message range within the as-rendered text. | ||
* Must be called from main thread | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, so invalidating the truncation text must be done while we still hold the lock. Because after we release it, another thread may set a new truncation text that will then be cleared by this thread, or may draw this soon-to-be-invalidated text.
Let's leave a comment/explaination here.