Repository navigation
Commit 7fc58f2
Avoid copying text fragments and re-attributing runs when building NSAttributedString (#59154)
Summary:
`RCTNSAttributedStringFromAttributedString` (`ReactCommon/react/renderer/textlayoutmanager/platform/ios/.../RCTAttributedTextUtils.mm`) converts a paragraph's `AttributedString` into the `NSAttributedString` TextKit 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:
- the loop iterates `getFragments()` by value (`for (auto fragment : ...)`), copying each `Fragment`: its `std::string`, its `TextAttributes`, and a `ShadowView` with three `shared_ptr` members (props, event emitter, state), so three atomic retain/release pairs per fragment;
- the event emitter is attached in a second `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. For an attachment, the string from `attributedStringWithAttachment:` is also copied with `mutableCopy` before that pass.
**With this change.** Fragments are iterated by reference, and `RCTNSAttributedStringFragmentFromFragment` creates each fragment's string with all of its attributes at once, including the event emitter, for text and attachment fragments alike. The second `addAttributes:range:` pass and the wrapper function that ran it are gone. The attachment string is created with `initWithString:attributes:` from `NSAttachmentCharacter` and `NSAttachmentAttributeName`, which is what `attributedStringWithAttachment:` produces, so it needs no copy. Every run ends up with the same attributes as before.
## Changelog:
[IOS] [CHANGED] - Build a paragraph's NSAttributedString without copying each text fragment or re-attributing its runs
Pull Request resolved: #59154
Test Plan:
- `node scripts/clang-format.js --check` on the changed file passes.
- RNTester built with `xcodebuild` in Release and run on a physical iPhone 15 Pro (iOS 26.6.1).
- **Same output.** A local build ran `main`'s conversion next to this one for every paragraph and compared the two results run by run: string, run ranges and every attribute, with attachments compared by bounds and image and event emitters by the emitter they wrap. Scrolling through the RNTester Text examples, which include inline views and pressable nested text, and mounting 1000 labels five times in the Playground compared over 31,000 conversions, with 217 attachment fragments and over 32,000 fragments carrying an event emitter. There were no differences, and the same check reports an emitter removed on purpose.
- **Measurement.** In the same build, both conversions were timed on the same input, alternating which ran first. Mounting 1000 system-font labels five times in the RNTester Playground (27,750 conversions): 135.3 ms with `main`'s conversion and 99.4 ms with this change, −26% of the conversion step. The presses were driven by [Argent](https://github.com/software-mansion/argent) on the physical iPhone.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed By: cipolleschi
Differential Revision: D126056330
Pulled By: javache
fbshipit-source-id: 620dfa63b57e905f98ed57a1e304d14db9915e721 parent 6e2edef commit 7fc58f2
1 file changed
Lines changed: 12 additions & 22 deletions
File tree
- packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
405 | 405 | | |
406 | 406 | | |
407 | 407 | | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
408 | 411 | | |
409 | 412 | | |
410 | 413 | | |
| |||
415 | 418 | | |
416 | 419 | | |
417 | 420 | | |
418 | | - | |
| 421 | + | |
| 422 | + | |
419 | 423 | | |
420 | 424 | | |
421 | 425 | | |
422 | 426 | | |
423 | | - | |
| 427 | + | |
424 | 428 | | |
425 | 429 | | |
426 | 430 | | |
427 | 431 | | |
428 | 432 | | |
429 | 433 | | |
430 | | - | |
431 | | - | |
432 | | - | |
| 434 | + | |
433 | 435 | | |
434 | | - | |
435 | | - | |
436 | | - | |
437 | | - | |
438 | | - | |
439 | | - | |
440 | | - | |
441 | 436 | | |
| 437 | + | |
442 | 438 | | |
443 | | - | |
444 | | - | |
445 | | - | |
446 | | - | |
447 | | - | |
448 | | - | |
449 | | - | |
| 439 | + | |
450 | 440 | | |
451 | 441 | | |
452 | | - | |
| 442 | + | |
453 | 443 | | |
454 | 444 | | |
455 | 445 | | |
| |||
464 | 454 | | |
465 | 455 | | |
466 | 456 | | |
467 | | - | |
| 457 | + | |
468 | 458 | | |
469 | | - | |
| 459 | + | |
470 | 460 | | |
471 | 461 | | |
472 | 462 | | |
| |||
0 commit comments