Skip to content

Commit 14c4ad2

Browse files
alwxclaude
andcommitted
fix(ios): Break RNSentryOnDrawReporterView retain cycle in emit-new-frame block
The emit-new-frame block created in `createEmitNewFrameEvent` captured `self` strongly, closing a `view → framesListener → block → view` loop that kept every TTID/TTFD reporter view and its `RNSentryFramesTrackerListener` alive for the process lifetime. Capture `self` weakly so the trio can be deallocated once React Native releases the view. Fixes #6440 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent c7cec96 commit 14c4ad2

3 files changed

Lines changed: 31 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- Fix iOS time-to-initial-display fallback spans reporting a spurious `deadline_exceeded` status and inflated duration ([#6438](https://github.com/getsentry/sentry-react-native/pull/6438))
1818
- Fix `TypeError` when `showFeedbackForm`/`showFeedbackButton`/`showScreenshotButton` is called before `FeedbackFormProvider` mounts ([#6435](https://github.com/getsentry/sentry-react-native/pull/6435))
1919
- Fix orphaned TTID/TTFD spans in the trace view ([#6437](https://github.com/getsentry/sentry-react-native/pull/6437))
20+
- Fix iOS retain cycle in `RNSentryOnDrawReporterView` leaking TTID/TTFD reporter views and their frame-tracker listeners ([#6440](https://github.com/getsentry/sentry-react-native/issues/6440))
2021

2122
### Internal
2223

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryOnDrawReporterTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,22 @@ final class RNSentryOnDrawReporterTests: XCTestCase {
145145

146146
emitNewFrameCallback!(1)
147147
}
148+
149+
func testReporterViewIsDeallocatedAfterRelease() {
150+
// Guards against the retain cycle described in
151+
// https://github.com/getsentry/sentry-react-native/issues/6440 where the
152+
// emit-new-frame block captured `self` strongly, keeping the view (and
153+
// its frames-tracker listener) alive forever.
154+
weak var weakReporter: RNSentryOnDrawReporterView?
155+
156+
autoreleasepool {
157+
let reporter = RNSentryOnDrawReporterView.createWithMockedListener()
158+
reporter!.initialDisplay = true
159+
reporter!.parentSpanId = spanId
160+
reporter!.didSetProps(["initialDisplay", "parentSpanId"])
161+
weakReporter = reporter
162+
}
163+
164+
XCTAssertNil(weakReporter, "RNSentryOnDrawReporterView leaked due to a retain cycle")
165+
}
148166
}

packages/core/ios/RNSentryOnDrawReporter.m

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,29 @@ - (instancetype)init
4040

4141
- (RNSentryEmitNewFrameEvent)createEmitNewFrameEvent
4242
{
43+
__weak __typeof__(self) weakSelf = self;
4344
return ^(NSNumber *newFrameTimestampInSeconds) {
44-
self->isListening = NO;
45+
__strong __typeof__(weakSelf) strongSelf = weakSelf;
46+
if (strongSelf == nil) {
47+
return;
48+
}
49+
50+
strongSelf->isListening = NO;
4551

46-
if (![_parentSpanId isKindOfClass:[NSString class]]) {
52+
if (![strongSelf->_parentSpanId isKindOfClass:[NSString class]]) {
4753
return;
4854
}
4955

50-
if (self->_fullDisplay) {
56+
if (strongSelf->_fullDisplay) {
5157
[RNSentryTimeToDisplay
52-
putTimeToDisplayFor:[@"ttfd-" stringByAppendingString:self->_parentSpanId]
58+
putTimeToDisplayFor:[@"ttfd-" stringByAppendingString:strongSelf->_parentSpanId]
5359
value:newFrameTimestampInSeconds];
5460
return;
5561
}
5662

57-
if (self->_initialDisplay) {
63+
if (strongSelf->_initialDisplay) {
5864
[RNSentryTimeToDisplay
59-
putTimeToDisplayFor:[@"ttid-" stringByAppendingString:self->_parentSpanId]
65+
putTimeToDisplayFor:[@"ttid-" stringByAppendingString:strongSelf->_parentSpanId]
6066
value:newFrameTimestampInSeconds];
6167
return;
6268
}

0 commit comments

Comments
 (0)