Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TextKitでのシンタックスハイライト高速化 / Optimize Syntax Highlight with TextKit

1024jp
August 27, 2022

TextKitでのシンタックスハイライト高速化 / Optimize Syntax Highlight with TextKit

macOS native symposium (https://macos-native.github.io) #08 登壇資料

テキスト描画の世界は複雑で、何気ない操作もデータ量が大きくなると簡単に処理のボトルネックと化します。しかし、何がそんなに時間を要するのでしょうか。macOS、iOS共にテキスト描画を司るTextKitの癖を読み抜くことで、愚直な実装からシンタックスハイライトの適用を高速化していく過程を理屈を追いながら解説します。

1024jp

August 27, 2022
Tweet

More Decks by 1024jp

Other Decks in Programming

Transcript

  1. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp ࣗݾ঺հ CotEditor Gapplin Qli application

    works plain-text editor SVG viewer movie player macOS meet-up icon works @1024jp hobby macOS developer/designer
  2. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp γϯλοΫεϋΠϥΠτ <!DOCTYPE html> <html lang="en">

    <head prefix="og: http://ogp.me/ns#"> <meta charset="UTF-8"/> <meta name="description" content="Text Editor for macOS"/> <title>CotEditor -Text Editor for macOS</title> <meta property="og:type" content="website"/> <meta property="og:url" content="https://coteditor.com"/> <meta property="og:title" content="CotEditor"/> <meta property="og:description" content="Text Editor for macOS"/> <meta property="og:image" content="https://coteditor.com/img/appicon/ [email protected]"/> <meta property="og:locale" content="en_US"/> <meta property="og:locale:alternate" content="ja_JP"/> <meta property="og:locale:alternate" content="tr_TR"/> <meta property="twitter:card" content="summary"/> <meta property="twitter:creator" content="@CotEditor"/> <link rel="shortcut icon" href="favicon.png" type="image/png" sizes="16x16"/> <link rel="shortcut icon" href="[email protected]" type="image/png" sizes="32x32"/> <link rel="stylesheet" href="css/slider.css"/> <link rel="stylesheet" href="css/common.css"/> <!DOCTYPE html> <html lang="en"> <head prefix="og: http://ogp.me/ns#"> <meta charset="UTF-8"/> <meta name="description" content="Text Editor for macOS"/> <title>CotEditor -Text Editor for macOS</title> <meta property="og:type" content="website"/> <meta property="og:url" content="https://coteditor.com"/> <meta property="og:title" content="CotEditor"/> <meta property="og:description" content="Text Editor for macOS"/> <meta property="og:image" content="https://coteditor.com/img/appicon/ [email protected]"/> <meta property="og:locale" content="en_US"/> <meta property="og:locale:alternate" content="ja_JP"/> <meta property="og:locale:alternate" content="tr_TR"/> <meta property="twitter:card" content="summary"/> <meta property="twitter:creator" content="@CotEditor"/> <link rel="shortcut icon" href="favicon.png" type="image/png" sizes="16x16"/> <link rel="shortcut icon" href="[email protected]" type="image/png" sizes="32x32"/> <link rel="stylesheet" href="css/slider.css"/> <link rel="stylesheet" href="css/common.css"/>
  3. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp ʢ$PU&EJUPSͰʣٻΊΒΕ͍ͯΔ͜ͱ ✔ 5FYU,JUΛ࢖͏ ! NBD04ͷػೳΛ࠷େݶڗड͢Δ

    ! ଟ༷ͳݴޠʹରԠ͢ΔʢFHॎॻ͖ CJEJςΩετ ϓϩϙʔγϣφϧϑΥϯτ 7PJDF0WFS FUDʣ 5FYU,JU͸େมʹϦον͕ͩɺ ͦͷ෼΍͍ͬͯΔ͜ͱ͕ଟ͍ TextKit 2には期待してるよ… = 一億字 ॲཧͷϘτϧωοΫ͕ଘࡏ͢Δ ✔ ͲΜͳॻྨ͕։͔Εͯ΋ͦΕͳΓʹॲཧ͕Ͱ͖Δ ! ྫ͑͹.#   ࣈ ͷ9.-ϑΝΠϧ͕։͔Εͨͱ͖Ͳ͏ͳΔͷ͔ ! "
  4. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp γϯλοΫεϋΠϥΠτΛߴ଎Խ͢ΔͨΊͷςΫχοΫ ᶃ ඞཁͳՕॴ͚ͩύʔε ᶄ όοΫάϥ΢ϯυεϨουͰύʔεΛ͢Δ

    ᶈ EJTQMBZͷWBMJEBUJPOΛ஗Ԇ͢Δ ᶉ MBZPVUNBOBHFSʹۃྗSBOHFܭࢉΛͤ͞ͳ͍ ᶅ UFNQPSBSZBUUSJCVUFTΛ࢖͏ ᶆ ฒྻʹॲཧ͢Δ ᶇ όοΫάϥ΢υʹ͍Δؒʹద༻಺༰Λ࠷খԽ͢Δ ϋʔυίΞ ॳ ڃ ج ຊ Y. Niwa: iOS ͷΩʔϘʔυͱจࣈೖྗͷ͢΂ͯ, iOSDC Japan 2020, 2020-09
  5. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp ύʔεൣғ <!DOCTYPE html> <html lang="en">

    <head prefix="og: http://ogp.me/ns#"> <meta charset="UTF-8"/> <meta name="description" content="Text Editor for macOS"/> <title>CotEditor -Text Editor for macOS</title> <meta property="og:type" content="website"/> <meta property="og:url" content="https://coteditor.com"/> <meta property="og:title" content="CotEditor"/> <meta property="og:description" content="Text Editor for macOS"/> <meta property="og:image" content="https://coteditor.com/img/appicon/[email protected]"/> <meta property="og:locale" content="en_US"/> <meta property="og:locale:alternate" content="ja_JP"/> <meta property="og:locale:alternate" content="tr_TR"/> <meta property="twitter:card" content="summary"/> <meta property="twitter:creator" content="@CotEditor"/> <link rel="shortcut icon" href="favicon.png" type="image/png" sizes="16x16"/> <link rel="shortcut icon" href="[email protected]" type="image/png" sizes="32x32"/> <link rel="stylesheet" href="css/slider.css"/> <link rel="stylesheet" href="css/common.css"/> <link rel="stylesheet" href="css/top.css"/> <script> switch (navigator.language.substr(0,2)) { case 'ja': window.location = 'index.ja'; break; case 'tr': window.location = 'index.tr'; break; } </script> <script async="async" src="js/common.js"></script> </head> <body> <!-- <a id="beta" href="beta">try <strong>beta</strong></a> --> <header> <nav><ul> <li><a href="archives">Archives</a></li> <li><a href="contact">Contact</a></li> <li><a href="https://twitter.com/CotEditor" rel="external">Twitter</a></li> </ul></nav> </header> <main> <section id="top"> <img src="img/appicon/[email protected]" srcset="img/appicon/[email protected] 2x" width="256" alt="[appIcon]"/> <h1>CotEditor</h1> <p>The Plain-Text Editor for macOS</p> <section id="latest"> <p class="handwriting">It's free!</p> <a href="https://itunes.apple.com/app/coteditor/id1024640650?ls=1" class="download"> <img src="img/MacAppStore.svg" alt="Download on the Mac App Store"/> </a> </section> </section> <section id="screenshots" class="slider"> <input type="radio" name="control-operator" id="item1" checked="checked"/> <input type="radio" name="control-operator" id="item2"/> <input type="radio" name="control-operator" id="item3"/> <input type="radio" name="control-operator" id="item4"/> <input type="radio" name="control-operator" id="item5"/> <input type="radio" name="control-operator" id="item6"/> <div class="slides"> <figure> <img src="img/screenshots/[email protected]" width="735" height="650" alt="[screenshot]"/> </figure> <figure> <img src="img/screenshots/[email protected]" width="735" height="650" alt="[screenshot: Dark Mode]"/> </figure> <figure> <img src="img/screenshots/[email protected]" width="735" height="650" alt="[screenshot: tools]"/> </figure> <figure> <img src="img/screenshots/[email protected]" width="735" height="650" alt="[screenshot: vertical orientation]"/> </figure> <figure> <img src="img/screenshots/[email protected]" width="682" height="613" alt="[screenshot: preferences window]"/> </figure> </div> <section id="top"> <img src="img/appicon/[email protected]" srcset="img/appicon/[email protected] 2x" width="256" alt="[appIcon]"/> <h1>CotEditor</h1> <p>The Plain-Text Editor for macOS</p> <section id="latest"> <p class="handwriting">It's free!</p> <a href="https://itunes.apple.com/app/coteditor/id1024640650?ls=1" class="download"> <img src="img/MacAppStore.svg" alt="Download on the Mac App Store"/> </a> </section> </section> <section id="screenshots" class="slider"> <input type="radio" name="control-operator" id="item1" checked="checked"/> <input type="radio" name="control-operator" id="item2"/> <input type="radio" name="control-operator" id="item3"/> <input type="radio" name="control-operator" id="item4"/> <input type="radio" name="control-operator" id="item5"/> <input type="radio" name="control-operator" id="item6"/> edited in window document edited characters <img src="img/MacAppStore.svg" alt="Download on the Mac App Store"/> appx. 1,000 จࣈ
  6. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp γϯλοΫεϋΠϥΠτͷྲྀΕ ϋ Π ϥ Π

    τ ׬ ྃ ς Ω ε τ ม ߋ จ ࣈ ྻ ύ ồ ε ϋ Π ϥ Π τ ద ༻ start end ύ ồ ε ൣ ғ ܾ ఆ
  7. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp γϯλοΫεϋΠϥΠτͷྲྀΕ Main Thread ϋ Π

    ϥ Π τ ׬ ྃ ς Ω ε τ ม ߋ จ ࣈ ྻ ύ ồ ε ϋ Π ϥ Π τ ద ༻ start end ύ ồ ε ൣ ғ ܾ ఆ Highlighting… ! Background Threads
  8. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp let string = NSString(string: textStorage.string)

    as String Task.detached { let highlights = parser.parse(string, in: range) await MainActor.run { textStorage.apply(highlights) } } όοΫάϥ΢ϯυεϨουʹTUSJOHΛ౉͢ let string = textStorage.string Task.detached { let highlights = parser.parse(string, in: range) await MainActor.run { textStorage.apply(highlights) } } NSBigMutableString crash !! NSTaggedPointerString or __NSCFString type(of: string) // Swift.String NSTextStorage @property(readonly, copy) NSString *string; let textStorage.apply(highlights) ! (string as AnyObject).className // NSBigMutableString let highlights = parser.parse(textStorage.string, in: range)
  9. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp 5FNQPSBSZBUUSJCVUFT func addTemporaryAttributes(_: forCharacterRange: Discussion

    ... Currently the only temporary attributes recognized are those that do not affect layout (colors, underlines, and so on). func addAttributes(_:range:) I am dogco w. I am dogcow. NSTextLayoutManagerͷrendering attributesʹ૬౰ Temporary attributes NSLayoutManager Attributes NSTextStorage
  10. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp 5FNQPSBSZBUUSJCVUFT for range in ranges

    { layoutManager.addTemporaryAttributes(attributes, forCharacterRange: range) } for range in ranges { textStorage.addAttribute(attributes, range: range) } ? s 3.35 s textStorage.beginEditing() for range in ranges { textStorage.addAttribute(attributes, range: range) } textStorage.endEditing() 4.46 s Temporary attributes NSLayoutManager Attributes NSTextStorage
  11. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp γϯλοΫεϋΠϥΠτͷྲྀΕ Main Thread ϋ Π

    ϥ Π τ ׬ ྃ ς Ω ε τ ม ߋ จ ࣈ ྻ ύ ồ ε ϋ Π ϥ Π τ ద ༻ start end ύ ồ ε ൣ ғ ܾ ఆ Highlighting… ! Background Threads 3.35 s 0.00 s 16.23 s
  12. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp γϯλοΫεϋΠϥΠτͷύʔε Text Highlighted <dogcow name="clarus"

    type="dog&amp;cow" year="1987">moof</dogcow> <dogcow name="clarus" type="dog&amp;cow" year="1987">moof</dogcow>
  13. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp <dogcow name="clarus" type="dog&amp;cow" year="1987">moof</dogcow> <dogcow

    name="clarus" type="dog&amp;cow" year="1987">moof</dogcow> γϯλοΫεϋΠϥΠτͷύʔε Elements Elements Attributes Numbers Strings Characters
  14. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp var highlights: [SyntaxType: [NSRange]] =

    [.elements: [NSRange(), NSRange()], .attributes: [NSRange(), NSRange(), ...], .strings: [NSRange(), NSRange(), ...], .characters: [NSRange()]] ύʔε಺༰Λద༻͢Δ Text Background Threads Highlighted Main Thread apply NSLayoutManager SyntaxParser parse Highlights <dogcow name="clarus" type="dog&amp;cow" year="1987">moof</dogcow> <dogcow name="clarus" type="dog&amp;cow" year="1987">moof</dogcow>
  15. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp γϯλοΫεϋΠϥΠτͷྲྀΕ Main Thread ϋ Π

    ϥ Π τ ׬ ྃ ς Ω ε τ ม ߋ จ ࣈ ྻ ύ ồ ε ϋ Π ϥ Π τ ద ༻ start end ύ ồ ε ൣ ғ ܾ ఆ Highlighting… ! Background Threads 3.35 s 6.44 s 0.00 s 16.23 s
  16. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp ϋΠϥΠτ಺༰Λ/4-BZPVU.BOBHFSʹద༻͢Δ func apply(highlights: [SyntaxType: [NSRange]],

    range highlightRange: NSRange, theme: Theme) { layoutManager.removeTemporaryAttribute(.foregroundColor, forCharacterRange: highlightRange) for type in SyntaxType.allCases { guard let ranges = highlights[type]?.compactMap({ $0.intersection(highlightRange) }), !ranges.isEmpty else { continue } let color = theme.style(for: type).color for range in ranges { layoutManager.addTemporaryAttribute(.foregroundColor, value: color, forCharacterRange: range) } } } 3.33 s 3.35 s – NSLayoutManager.invalidateDisplay(forCharacterRange:) – [NSMutableRLEArray replaceObjectsInRange:withObject:length:] – [__NSDictonaryM mutableCopyWithZone:] 2.58 s 0.73 s 0.005 s
  17. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp JOWBMJEBUF%JTQMBZ GPS$IBSBDUFS3BOHF ͱ͸ func invalidateDisplay(forCharacterRange

    charRange: NSRange) Summary Invalidates display for the specified character range. Discussion Parts of the range that are not laid out are remembered and redisplayed later when the layout is available. Does not actually cause layout. fi fi Temporary attributes NSLayoutManager Attributes NSTextStorage fi fi
  18. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp %JTQMBZΛຖճJOWBMJEBUF͠ͳ͍Α͏ʹ͢Δ class LayoutManager: NSLayoutManager {

    private var ignoresDisplayValidation = false func groupTemporaryAttributesUpdate(in range: NSRange, work: () throws -> Void) rethrows { self.ignoresDisplayValidation = true defer { self.ignoresDisplayValidation = false self.invalidateDisplay(forCharacterRange: range) } try work() } override func invalidateDisplay(forCharacterRange charRange: NSRange) { if self.ignoresDisplayValidation { return } super.invalidateDisplay(forCharacterRange: charRange) } }
  19. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp %JTQMBZΛຖճJOWBMJEBUF͠ͳ͍Α͏ʹ͢Δ func apply(highlights: [SyntaxType: [NSRange]],

    range highlightRange: NSRange, theme: Theme) { layoutManager.removeTemporaryAttribute(.foregroundColor, forCharacterRange: highlightRange) for type in SyntaxType.allCases { guard let ranges = highlights[type]?.compactMap({ $0.intersection(highlightRange) }), !ranges.isEmpty else { continue } let color = theme.style(for: type).color for range in ranges { layoutManager.addTemporaryAttribute(.foregroundColor, value: color, forCharacterRange: range) } } } 3.33 s 3.35 s – NSLayoutManager.invalidateDisplay(forCharacterRange:) – [NSMutableRLEArray replaceObjectsInRange:withObject:length:] – [__NSDictonaryM mutableCopyWithZone:] 2.58 s 0.73 s 0.005 s
  20. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp %JTQMBZΛຖճJOWBMJEBUF͠ͳ͍Α͏ʹ͢Δ func apply(highlights: [SyntaxType: [NSRange]],

    range highlightRange: NSRange, theme: Theme) { layoutManager.groupTemporaryAttributesUpdate(in: highlightRange) { layoutManager.removeTemporaryAttribute(.foregroundColor, forCharacterRange: highlightRange) for type in SyntaxType.allCases { guard let ranges = highlights[type]?.compactMap({ $0.intersection(highlightRange) }), !ranges.isEmpty else { continue } let color = theme.style(for: type).color for range in ranges { layoutManager.addTemporaryAttribute(.foregroundColor, value: color, forCharacterRange: range) } } } } 0.736 s 0.76 s – NSLayoutManager.invalidateDisplay(forCharacterRange:) – [NSMutableRLEArray replaceObjectsInRange:withObject:length:] – [__NSDictonaryM mutableCopyWithZone:] 2.58 s 0.73 s 0.005 s
  21. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp %JTQMBZΛຖճJOWBMJEBUF͠ͳ͍Α͏ʹ͢Δ func apply(highlights: [SyntaxType: [NSRange]],

    range highlightRange: NSRange, theme: Theme) { layoutManager.groupTemporaryAttributesUpdate(in: highlightRange) { layoutManager.removeTemporaryAttribute(.foregroundColor, forCharacterRange: highlightRange) for type in SyntaxType.allCases { guard let ranges = highlights[type]?.compactMap({ $0.intersection(highlightRange) }), !ranges.isEmpty else { continue } let color = theme.style(for: type).color for range in ranges { layoutManager.addTemporaryAttribute(.foregroundColor, value: color, forCharacterRange: range) } } } } 0.736 s 0.76 s – [NSMutableRLEArray replaceObjectsInRange:withObject:length:] – [__NSDictonaryM mutableCopyWithZone:] 0.73 s 0.005 s
  22. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp BEE"UUSJCVUFͰԿ͕ى͍ͬͯ͜Δͷ͔ ᶅ 4XJGUˠ0CKFDUJWF$ʹίετֻ͕͔͍ͬͯΔ ˠ͚ͩ͜͜શ෦0CKFDUJWF$Ͱॻ͍ͯΈΔ ᶃ

    ࣮͸·ͩඳըपΓͷॲཧΛ͍ͯ͠Δ ద༻ճ਺ ࣌ؒ (ms) , , , , , , , , O(n²) ͍ΘΏΔ つらい ܏޲Λ௫Ή ᶄ /4$PMPS͕ͳΜ͔ແବʹҠಈͯ͠Δ ˠVOEFSMJOF4UZMFʹม͑ͯΈΔʢWBMVF͕*OUͳͷͰʣ ˠ/4.VUBCMF3-&"SSBZ͔ͩΒҧ͏ – [NSMutableRLEArray replaceObjectsInRange:withObject:length:] Ծઆ 0.73 s ≒ 4 min. –_platform_memmove
  23. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp ςετ༻ͷ࠷খίʔυ let layoutManager = LayoutManager()

    // prepare string let length = 7_000_000 let string = String(repeating: "a", count: length) let textStorage = NSTextStorage(string: string) textStorage.addLayoutManager(layoutManager) // prepare temporary attributes let ranges = (0..<100_000) .map { _ in Int.random(in: (0..<length-1)) } .map { NSRange(location: $0, length: 1) } // add temporary attributes to layoutManager for range in ranges { layoutManager.addTemporaryAttribute(.foregroundColor, value: NSColor.red, forCharacterRange: range) } 2.46 s
  24. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp var highlights: [SyntaxType: [NSRange]] =

    [.elements: [NSRange(), NSRange()], .attributes: [NSRange(), NSRange(), ...], .strings: [NSRange(), NSRange(), ...], .characters: [NSRange()]] ύʔε಺༰Λద༻͢Δ Text Background Threads Highlighted Main Thread apply NSLayoutManager SyntaxParser parse Highlights <dogcow name="clarus" type="dog&amp;cow" year="1987">moof</dogcow> <dogcow name="clarus" type="dog&amp;cow" year="1987">moof</dogcow>
  25. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp struct Highlight { var range:

    NSRange var type: SyntaxType } ύʔε಺༰Λద༻͢Δ Text Background Threads Highlighted Main Thread apply NSLayoutManager SyntaxParser parse Highlights <dogcow name="clarus" type="dog&amp;cow" year="1987">moof</dogcow> <dogcow name="clarus" type="dog&amp;cow" year="1987">moof</dogcow> var highlights: [Highlight]
  26. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp ιʔτࡁΈͷϋΠϥΠτΛద༻͢Δ func apply(highlights: [SyntaxType: [NSRange]],

    range highlightRange: NSRange, theme: Theme) { layoutManager.groupTemporaryAttributesUpdate(in: highlightRange) { layoutManager.removeTemporaryAttribute(.foregroundColor, forCharacterRange: highlightRange) for type in SyntaxType.allCases { guard let ranges = highlights[type]?.compactMap({ $0.intersection(highlightRange) }), !ranges.isEmpty else { continue } let color = theme.style(for: type).color for range in ranges { layoutManager.addTemporaryAttribute(.foregroundColor, value: color, forCharacterRange: range) } } } } 0.736 s 0.76 s – [NSMutableRLEArray replaceObjectsInRange:withObject:length:] – [__NSDictonaryM mutableCopyWithZone:] 0.73 s 0.005 s
  27. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp ιʔτࡁΈͷϋΠϥΠτΛద༻͢Δ func apply(highlights: [Highlight], range

    highlightRange: NSRange, theme: Theme) { layoutManager.groupTemporaryAttributesUpdate(in: highlightRange) { layoutManager.removeTemporaryAttribute(.foregroundColor, forCharacterRange: highlightRange) for highlight in highlights { let color = theme.style(for: highlight.type).color layoutManager.addTemporaryAttribute(.foregroundColor, value: color, forCharacterRange: highlight.range) } } } 0.044 s 0.049 s 0.000 s – [NSMutableRLEArray replaceObjectsInRange:withObject:length:] – [__NSDictonaryM mutableCopyWithZone:] 0.005 s
  28. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp γϯλοΫεϋΠϥΠτͷྲྀΕ Main Thread ϋ Π

    ϥ Π τ ׬ ྃ ς Ω ε τ ม ߋ จ ࣈ ྻ ύ ồ ε ϋ Π ϥ Π τ ద ༻ start end ύ ồ ε ൣ ғ ܾ ఆ Highlighting… ! Background Threads 0.05 s 3.35 s 6.44 s 0.00 s 16.23 s
  29. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp γϯλοΫεϋΠϥΠτΛߴ଎Խ͢ΔͨΊͷςΫχοΫ ᶃ ඞཁͳՕॴ͚ͩύʔε ᶄ όοΫάϥ΢ϯυεϨουͰύʔεΛ͢Δ

    ᶈ EJTQMBZͷWBMJEBUJPOΛ஗Ԇ͢Δ ᶉ MBZPVUNBOBHFSʹۃྗSBOHFܭࢉΛͤ͞ͳ͍ ᶅ UFNQPSBSZBUUSJCVUFTΛ࢖͏ ᶆ ฒྻʹॲཧ͢Δ ᶇ όοΫάϥ΢υʹ͍Δؒʹద༻಺༰Λ࠷খԽ͢Δ ϋʔυίΞ ॳ ڃ ج ຊ # "
  30. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp 5FYU,JU͸ʁ func apply(highlights: [Highlight], range

    highlightRange: NSRange, theme: Theme) { layoutManager.groupTemporaryAttributesUpdate(in: highlightRange) { layoutManager.removeTemporaryAttribute(.foregroundColor, forCharacterRange: highlightRange) for highlight in highlights { let color = theme.style(for: highlight.type).color layoutManager.addTemporaryAttribute(.foregroundColor, value: color, forCharacterRange: highlight.range) } } } 0.05 s TextKit 1 NSLayoutManager
  31. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp 5FYU,JU͸ʁ func apply(highlights: [Highlight], range

    highlightRange: NSRange, theme: Theme) { if let location = layoutManager.location(layoutManager.documentRange.location, offsetBy: highlightRange.location), let end = layoutManager.location(location, offsetBy: highlightRange.length), let textRange = NSTextRange(location: location, end: end) { layoutManager.removeRenderingAttribute(.foregroundColor, for: textRange) } for highlight in highlights { guard let location = layoutManager.location(layoutManager.documentRange.location, offsetBy: highlight.range.location), let end = layoutManager.location(location, offsetBy: highlight.range.length), let textRange = NSTextRange(location: location, end: end) else { continue } let color = theme.style(for: highlight.type).color layoutManager.addRenderingAttribute(.foregroundColor, value: highlight.color, for: textRange) } } 0.39 s TextKit 2 NSTextLayoutManager
  32. macOS native 5FYU,JUͰͷγϯλοΫεϋΠϥΠτߴ଎Խ ©2022 1024jp 5FYU,JU͸ʁ ᶃ ඞཁͳՕॴ͚ͩύʔε ᶄ όοΫάϥ΢ϯυεϨουͰύʔεΛ͢Δ

    ᶈ EJTQMBZͷWBMJEBUJPOΛ஗Ԇ͢Δ ᶉ MBZPVUNBOBHFSʹۃྗSBOHFܭࢉΛͤ͞ͳ͍ ᶅ UFNQPSBSZBUUSJCVUFTΛ࢖͏ ᶆ ฒྻʹॲཧ͢Δ ᶇ όοΫάϥ΢υʹ͍Δؒʹద༻಺༰Λ࠷খԽ͢Δ ϋʔυίΞ ॳ ڃ ج ຊ # "