From 6a7ed714cebcce29afa80b4acc4a3a110e11dbd6 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 10 Oct 2024 13:47:23 +0200 Subject: [PATCH] ref: Session replay performance for SwiftUI (#4419) Improved Performance for SwiftUI session replay --- CHANGELOG.md | 1 + Sources/Sentry/SentrySessionReplayIntegration.m | 2 +- Sources/Swift/Tools/SentryViewPhotographer.swift | 6 ++++++ Sources/Swift/Tools/UIRedactBuilder.swift | 6 +++++- .../SentryTests/SentryViewPhotographerTests.swift | 15 +++++++++++++++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2020971a43..2ed56b9fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ via the option `swizzleClassNameExclude`. ### Improvements - Serializing profile on a BG Thread (#4377) to avoid potentially slightly blocking the main thread. +- Session Replay performance for SwiftUI (#4419) ## 8.38.0-beta.1 diff --git a/Sources/Sentry/SentrySessionReplayIntegration.m b/Sources/Sentry/SentrySessionReplayIntegration.m index cd6df2e838..e2db6fc3c8 100644 --- a/Sources/Sentry/SentrySessionReplayIntegration.m +++ b/Sources/Sentry/SentrySessionReplayIntegration.m @@ -295,7 +295,7 @@ - (void)startWithOptions:(SentryReplayOptions *)replayOptions [self.sessionReplay startWithRootView:SentryDependencyContainer.sharedInstance.application.windows.firstObject - fullSession:[self shouldReplayFullSession:replayOptions.sessionSampleRate]]; + fullSession:shouldReplayFullSession]; [_notificationCenter addObserver:self selector:@selector(pause) diff --git a/Sources/Swift/Tools/SentryViewPhotographer.swift b/Sources/Swift/Tools/SentryViewPhotographer.swift index 322a7017a8..d553e4ea17 100644 --- a/Sources/Swift/Tools/SentryViewPhotographer.swift +++ b/Sources/Swift/Tools/SentryViewPhotographer.swift @@ -45,6 +45,7 @@ class SentryViewPhotographer: NSObject, SentryViewScreenshotProvider { dispatchQueue.dispatchAsync { let screenshot = UIGraphicsImageRenderer(size: imageSize, format: .init(for: .init(displayScale: 1))).image { context in + let imageRect = CGRect(origin: .zero, size: imageSize) context.cgContext.addRect(CGRect(origin: CGPoint.zero, size: imageSize)) context.cgContext.clip(using: .evenOdd) UIColor.blue.setStroke() @@ -52,11 +53,16 @@ class SentryViewPhotographer: NSObject, SentryViewScreenshotProvider { context.cgContext.interpolationQuality = .none image.draw(at: .zero) + var latestRegion: RedactRegion? for region in redact { let rect = CGRect(origin: CGPoint.zero, size: region.size) var transform = region.transform let path = CGPath(rect: rect, transform: &transform) + defer { latestRegion = region } + + guard latestRegion?.canReplace(as: region) != true && imageRect.intersects(path.boundingBoxOfPath) else { continue } + switch region.type { case .redact, .redactSwiftUI: (region.color ?? UIImageHelper.averageColor(of: context.currentImage, at: rect.applying(region.transform))).setFill() diff --git a/Sources/Swift/Tools/UIRedactBuilder.swift b/Sources/Swift/Tools/UIRedactBuilder.swift index 72ff985d95..cd3f0f3442 100644 --- a/Sources/Swift/Tools/UIRedactBuilder.swift +++ b/Sources/Swift/Tools/UIRedactBuilder.swift @@ -39,6 +39,10 @@ struct RedactRegion { self.type = type self.color = color } + + func canReplace(as other: RedactRegion) -> Bool { + size == other.size && transform == other.transform && type == other.type + } } class UIRedactBuilder { @@ -170,7 +174,7 @@ class UIRedactBuilder { } //The swiftUI type needs to appear first in the list so it always get masked - return swiftUIRedact + otherRegions.reversed() + return (otherRegions + swiftUIRedact).reversed() } private func shouldIgnore(view: UIView) -> Bool { diff --git a/Tests/SentryTests/SentryViewPhotographerTests.swift b/Tests/SentryTests/SentryViewPhotographerTests.swift index 3217e9dca7..69643f483d 100644 --- a/Tests/SentryTests/SentryViewPhotographerTests.swift +++ b/Tests/SentryTests/SentryViewPhotographerTests.swift @@ -180,6 +180,21 @@ class SentryViewPhotographerTests: XCTestCase { assertColor(pixel2, .white) } + func testSkipSameRegion() throws { + let label1 = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 25)) + label1.text = "Test" + label1.textColor = .red + + let label2 = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 25)) + label2.text = "Test" + label2.textColor = .green + + let image = try XCTUnwrap(prepare(views: [label1, label2])) + let pixel1 = color(at: CGPoint(x: 10, y: 10), in: image) + + assertColor(pixel1, .green) + } + private func assertColor(_ color1: UIColor, _ color2: UIColor) { let sRGBColor1 = color1.cgColor.converted(to: CGColorSpace(name: CGColorSpace.sRGB)!, intent: .defaultIntent, options: nil) let sRGBColor2 = color2.cgColor.converted(to: CGColorSpace(name: CGColorSpace.sRGB)!, intent: .defaultIntent, options: nil)