In mergeTokensByRowId, each tokensByRowId[rowId] = tokens assignment triggered the didSet observer (Swift Dictionary is a value type), causing tokenAttributedStringsByRowId.removeAll(), clampHorizontalOffsets(), and setNeedsDisplayForVisibleBounds() to fire N times — once per patched row. This defeated the incremental cache invalidation the method intended and multiplied layout work for large token patches. Add a _suppressTokensByRowIdDidSet flag so the didSet observer is skipped during the merge loop. The method already performs its own targeted cache invalidation (removeValue per row) and calls clampHorizontalOffsets() and setNeedsDisplayForVisibleBounds() once at the end.
Unified · Split
| @@ -689,8 +689,10 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { · 688 unmodified lines | ||
| 689 | 689 | setNeedsDisplayForVisibleBounds() |
| 690 | 690 | } |
| 691 | 691 | } |
| 692 | + private var _suppressTokensByRowIdDidSet = false | |
| 692 | 693 | var tokensByRowId: [String: [ReviewDiffNativeToken]] = [:] { |
| 693 | 694 | didSet { |
| 695 | + guard !_suppressTokensByRowIdDidSet else { return } | |
| 694 | 696 | tokenAttributedStringsByRowId.removeAll() |
| 695 | 697 | clampHorizontalOffsets() |
| 696 | 698 | setNeedsDisplayForVisibleBounds() |
| @@ -698,10 +700,12 @@ private final class ReviewDiffContentView: UIView, UIGestureRecognizerDelegate { · 1 unmodified line | ||
| 698 | 700 | } |
| 699 | 701 | |
| 700 | 702 | func mergeTokensByRowId(_ tokensPatch: [String: [ReviewDiffNativeToken]]) { |
| 703 | + _suppressTokensByRowIdDidSet = true | |
| 701 | 704 | tokensPatch.forEach { rowId, tokens in |
| 702 | 705 | tokensByRowId[rowId] = tokens |
| 703 | 706 | tokenAttributedStringsByRowId.removeValue(forKey: rowId) |
| 704 | 707 | } |
| 708 | + _suppressTokensByRowIdDidSet = false | |
| 705 | 709 | clampHorizontalOffsets() |
| 706 | 710 | setNeedsDisplayForVisibleBounds() |
| 707 | 711 | } |