Repository navigation
Avoid copying text fragments and re-attributing runs when building NSAttributedString - #59154
hryhoriiK97 wants to merge 1 commit into
Conversation
cipolleschi
left a comment
There was a problem hiding this comment.
This PR is doing a lot of changes that are unrelated from the PR title/purpose.
Also, most of them are changes that are not really needed: they are small refactoring that only adds complexity and take time to review.
Please do small focused PRs that do a single thing and do not add not-relevant changes to the PR
| [[NSMutableAttributedString attributedStringWithAttachment:attachment] mutableCopy]; | ||
| if (eventEmitterWrapper != nil) { | ||
| [nsAttributedStringFragment addAttribute:RCTAttributedStringEventEmitterKey | ||
| value:eventEmitterWrapper | ||
| range:NSMakeRange(0, nsAttributedStringFragment.length)]; |
There was a problem hiding this comment.
this changes the behavior of React Native and has nothing to do with the PR title.
Before we were not attaching the event emitter if the fragment was an attachment, now we are.
If you want this change, please create a separate PR with the proper title and summary. Let's have small and focused PRs, please.
There was a problem hiding this comment.
Thanks for the review!
I believe this one is not a behavior change: on main, the same addAttributes call already set the emitter on attachment fragments too (RCTNSAttributedStringFragmentWithAttributesFromFragment applied it to both kinds). To make that clearer, I've reworked the diff so the attachment code stays untouched - only text fragments now receive the emitter in their initial attributes. Happy to adjust if you'd prefer a different shape 🙂
1343122 to
3fee4c7
Compare
3fee4c7 to
dc8e2cc
Compare
…AttributedString `RCTNSAttributedStringFromAttributedString` iterated the fragments by value, copying each one (its string, its text attributes and a shadow view with three shared pointers), and attached the event emitter to a text fragment in a second `addAttributes:range:` pass over the string it had just created with its attributes, which splits and re-merges the attribute runs. Iterate by reference and put the emitter into a text fragment's initial attribute dictionary. Attachment fragments keep the existing second pass, so the attributes on every run are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
dc8e2cc to
37b84be
Compare
@cipolleschi sorry about this! I've updated the PR so it does a single thing: it only avoids copying each text fragment and re-attributing the runs when building the NSAttributedString. The unrelated refactors are removed, and the title, description and measurements are updated to match the smaller diff. Thanks for the review! |
Summary:
RCTNSAttributedStringFromAttributedString(ReactCommon/react/renderer/textlayoutmanager/platform/ios/.../RCTAttributedTextUtils.mm) converts a paragraph'sAttributedStringinto theNSAttributedStringTextKit lays out and draws. It runs once per paragraph per measurement when the attributed-string cache misses, so for a screen of distinct labels it runs once per label.Current behavior. Two pieces of redundant work per fragment:
getFragments()by value (for (auto fragment : ...)), copying eachFragment: itsstd::string, itsTextAttributes, and aShadowViewwith threeshared_ptrmembers (props, event emitter, state), so three atomic retain/release pairs per fragment;addAttributes:range:pass over the fragment's string right after it was created with its attributes, which splits and re-merges the attribute runs TextKit just built.With this change. Fragments are iterated by reference, and a text fragment receives the event emitter in the initial attribute dictionary passed to
initWithString:attributes:. Attachment fragments are untouched: their string comes fromattributedStringWithAttachment:, and they keep the existingaddAttributes:range:pass. Onmainthat pass applied to both kinds whenever the fragment's parent has a component handle, so the attributes on every run, text and attachment alike, are the same as before.Changelog:
[IOS] [CHANGED] - Build a paragraph's NSAttributedString without copying each text fragment or re-attributing its runs
Test Plan:
node scripts/clang-format.js --checkon the changed file passes.xcodebuildin Release for a physical iPhone 15 Pro; the Text examples render as before.-[RCTTextLayoutManager measureAttributedString:...], summed per transaction, and Fabric's transaction telemetry for the layout total. Two builds of the same tree differing only in this file, with the font cache (Cache resolved custom fonts on iOS Fabric #59153) present in both so that font resolution does not dominate the conversion. The button presses were driven by Argent on the physical iPhone, so both builds ran the identical sequence (5 mounts and unmounts per font). iPhone 15 Pro, Release, medians of 4 to 5 mounts:fontFamily: 'Avenir'A second run of the same comparison gave 6.4 ms and 6.8 ms for the two cases, so the run-to-run spread is about 0.5 ms and the saving is 12 to 20% of the conversion step. The layout total moves within noise (47.6 → 46.9 ms system, 35.9 → 34.6 ms Avenir); the saving is confined to the conversion step, as expected.
🤖 Generated with Claude Code