Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
<!-- prettier-ignore-end -->

## Unreleased

### Fixes

- Fix iOS link failure `Undefined symbols: SentryInternalSwizzleApi.Mode.oncePerClass` when consuming the default prebuilt `Sentry.xcframework` ([#6465](https://github.com/getsentry/sentry-react-native/issues/6465))

## 8.19.0

### Features
Expand Down
46 changes: 0 additions & 46 deletions packages/core/ios/RNSentryInternal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,52 +182,6 @@ import Foundation
) {}
#endif

// MARK: - Swizzle

// `SentryInternalSwizzleApi` in sentry-cocoa has no platform gating.
// We enable the wrapper on every UIKit-capable platform (iOS/tvOS/visionOS
// โ€” matching `SENTRY_UIKIT_AVAILABLE`, which is what `SENTRY_HAS_UIKIT`
// resolves to in the RNSentry.mm caller). `os(iOS)` in Swift already
// covers Mac Catalyst, so no separate `targetEnvironment(macCatalyst)` is
// needed.
#if os(iOS) || os(tvOS) || os(visionOS)
/// Stable identity for the `RNSScreen.viewDidAppear:` swizzle so the underlying
/// `oncePerClass` bookkeeping in sentry-cocoa can dedupe re-entries.
private static var rnsScreenViewDidAppearKey: UInt8 = 0

/// Swizzles `-[RNSScreen viewDidAppear:]`, invoking `hook` before the original
/// implementation on every call. No-op when RNSScreen is unavailable.
@_spi(Private) @objc public static func swizzleRNSScreenViewDidAppear(hook: @escaping () -> Void) {
guard let cls = NSClassFromString("RNSScreen") else { return }
let selector = NSSelectorFromString("viewDidAppear:")

// `withUnsafePointer(to:)` scopes the pointer's validity to the closure
// body. Perform the entire swizzle call inside so we never rely on the
// pointer surviving beyond the closure. The backing storage is a
// `static var`, so the address itself stays stable across calls โ€”
// sentry-cocoa's `oncePerClass` bookkeeping continues to dedupe.
withUnsafePointer(to: &rnsScreenViewDidAppearKey) { keyPtr in
_ = SentrySDK.internal.swizzle.instanceMethod(
selector,
in: cls,
mode: .oncePerClass,
key: UnsafeRawPointer(keyPtr),
factory: { getOriginal in
let block: @convention(block) (AnyObject, ObjCBool) -> Void = { receiver, animated in
hook()
typealias OriginalIMP = @convention(c) (AnyObject, Selector, ObjCBool) -> Void
let original = unsafeBitCast(getOriginal(), to: OriginalIMP.self)
original(receiver, selector, animated)
}
return block as AnyObject
}
)
}
}
#else
@_spi(Private) @objc public static func swizzleRNSScreenViewDidAppear(hook: @escaping () -> Void) {}
#endif

// MARK: - Profiling

#if !(os(watchOS) || os(tvOS) || os(visionOS))
Expand Down
36 changes: 28 additions & 8 deletions packages/core/ios/RNSentryRNSScreen.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,41 @@

#if SENTRY_HAS_UIKIT

# if __has_include(<RNSentry/RNSentry-Swift.h>)
# import <RNSentry/RNSentry-Swift.h>
# else
# import "RNSentry-Swift.h"
# endif
# import "RNSentryDependencyContainer.h"
# import "RNSentryFramesTrackerListener.h"
// `SentrySwizzle.h` is a private header of `Sentry.framework`. We deliberately
// keep using the ObjC swizzle API here instead of routing through
// `SentrySDK.internal.swizzle` (the Swift `@_spi(Private)` equivalent):
// the Swift SPI enum case `SentryInternalSwizzleApi.Mode.oncePerClass` is not
// exported as a linkable symbol from sentry-cocoa's prebuilt static
// xcframework, so consumers on the default (`SENTRY_USE_XCFRAMEWORK` unset)
// path fail to link with `Undefined symbols: enum case for
// SentryInternalSwizzleApi.Mode.oncePerClass`. The ObjC `SentrySwizzle`
// class + `SentrySwizzleMode` enum have proper external symbols in the
// xcframework slice, so this path links cleanly for both the xcframework
// and source-built (`SENTRY_USE_XCFRAMEWORK=0`) configurations. See #6465.
# if __has_include(<Sentry/SentrySwizzle.h>)
# import <Sentry/SentrySwizzle.h>
# else
# import "SentrySwizzle.h"
# endif

@implementation RNSentryRNSScreen

+ (void)swizzleViewDidAppear
{
[RNSentryInternal swizzleRNSScreenViewDidAppearWithHook:^{
[[[RNSentryDependencyContainer sharedInstance] framesTrackerListener] startListening];
}];
Class rnsscreenclass = NSClassFromString(@"RNSScreen");
if (rnsscreenclass == nil) {
return;
}

SEL selector = NSSelectorFromString(@"viewDidAppear:");
SentrySwizzleInstanceMethod(rnsscreenclass, selector, SentrySWReturnType(void),
SentrySWArguments(BOOL animated), SentrySWReplacement({
[[[RNSentryDependencyContainer sharedInstance] framesTrackerListener] startListening];
SentrySWCallOriginal(animated);
}),
SentrySwizzleModeOncePerClass, (void *)selector);
}

@end
Expand Down
Loading