Conversation
Add a pure Objective-C SDK wrapper that mirrors the Sentry public API so it can be used from ObjC++ without modules (e.g. for React Native, Haxe, custom build systems). Includes: - SentryObjC product and target in Package.swift - Re-declared headers for all public types in Sources/SentryObjC/Public/ - Wrapper implementations for Swift-only types (Unit, Metric, MetricValue, AttributeContent, RedactRegionType) - iOS-ObjectiveCpp-NoModules sample using SentryObjC - sdk_objc_api.json generation via extract-objc-api.py for API stability CI - Makefile targets: build-spm-objc, verify-objc, generate-objc-api Refs GH-6342
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Documentation 📚
Internal Changes 🔧
🤖 This preview updates automatically when you update the PR. |
|
Add detailed documentation to core SentryObjC public APIs following Objective-C documentation best practices: - Document all properties with their purpose and behavior - Document all methods with parameters and return values - Add class-level documentation explaining usage context - Include notes about automatic behavior and warnings where applicable Files documented: - SentryObjCSDK: Main SDK entry point - SentryObjCOptions: Configuration options - SentryObjCEvent: Event data structure - SentryObjCScope: Contextual data container - SentryObjCBreadcrumb: Breadcrumb trail - SentryObjCUser: User identification - SentryObjCSpanProtocol: Performance tracing protocol Also adds changelog entry explaining the purpose of the SentryObjC wrapper SDK.
Add comprehensive documentation to exception, attachment, message, and tracing-related classes: - SentryObjCException: Exception information - SentryObjCAttachment: File attachments - SentryObjCMessage: Log messages - SentryObjCSpanContext: Span trace context - SentryObjCTransactionContext: Transaction context All classes now include detailed property and method documentation following Objective-C best practices.
Add comprehensive documentation to: - SentryObjCFrame: Stack frame with source location and context - SentryObjCStacktrace: Stack trace with frames and registers - SentryObjCThread: Thread information and crash state - SentryObjCMechanism: Error mechanism and handling context - SentryObjCDebugMeta: Debug symbols and binary metadata - SentryObjCRequest: HTTP request information - SentryObjCGeo: Geographical location data All classes now include detailed property and method documentation.
Add comprehensive documentation to: - SentryObjCReplayOptions: Session replay configuration with privacy controls - SentryObjCReplayApi: Runtime replay control and masking API - SentryObjCSamplingContext: Context for dynamic trace sampling decisions All properties and methods now include detailed documentation explaining their purpose, behavior, and usage.
Remove the conditional check for sdk_objc_api.json existence since an empty baseline file will be added to main in a follow-up PR. This simplifies the workflow logic.
Keep the original problem description explaining why modules don't work in ObjC++ projects, then show how SentryObjC solves this issue. This provides better context for readers to understand why SentryObjC exists. - Restore "Problem" section describing module import failures - Add "Solution: SentryObjC" section explaining how it solves the issue - List key differences from the main Sentry framework - Reference original issue #4543 and solution PR #6342
Sync with main to include latest changes and adopt new sample structure using projectReferences instead of SPM packages.
Replace Python-based regex parsing with clang AST dump and jq processing. The new approach: - Uses xcrun clang -ast-dump=json for reliable parsing - Extracts declarations via jq queries on intermediate files - Outputs structured JSON objects instead of string signatures - Migrates from Python to pure bash/jq pipeline Also standardize filename to sdk_api_objc.json to match the sdk_api.json and sdk_api_sentryswiftui.json naming convention. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Include the iOS-ObjectiveCpp-NoModules sample in CI build verification to ensure it continues to build successfully with the SentryObjC wrapper. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add the SentryObjC product and target to Package@swift-6.1.swift so the sample can reference it. Also revert iOS-ObjectiveCpp-NoModules sample to use SPM packages instead of projectReferences since SentryObjC is an SPM product, not an Xcode project target. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Complete documentation for: - SentryObjCAppStartMeasurement (app start types and timestamps) - SentryObjCAttributeContent (typed attribute values) - SentryObjCBaggage (distributed tracing propagation) - SentryObjCError (SDK error codes and helpers) - SentryObjCLogger (structured logging interface) - SentryObjCMechanismContext (crash metadata) - SentryObjCMetric (custom performance metrics) - SentryObjCMetricValue (metric value types) - SentryObjCNSError (serializable error representation) All public types, properties, methods, and enum values now have comprehensive documentation following Objective-C best practices. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
@sentry review |
📲 Install BuildsiOS
|
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| ce1ec47 | 1212.60 ms | 1247.61 ms | 35.02 ms |
| c5c7a29 | 1223.14 ms | 1258.55 ms | 35.41 ms |
| 994f7a3 | 1208.71 ms | 1240.54 ms | 31.83 ms |
| 49cc12d | 1227.33 ms | 1263.21 ms | 35.89 ms |
| ce900e7 | 1212.40 ms | 1244.57 ms | 32.18 ms |
| 44b7b59 | 1211.91 ms | 1248.90 ms | 36.99 ms |
| c958e9d | 1219.05 ms | 1256.44 ms | 37.40 ms |
| 48fa69f | 1221.60 ms | 1251.52 ms | 29.92 ms |
| 92bcc8f | 1233.43 ms | 1270.20 ms | 36.77 ms |
| ae8cece | 1216.83 ms | 1251.37 ms | 34.55 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| ce1ec47 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| c5c7a29 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 994f7a3 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 49cc12d | 24.14 KiB | 1.16 MiB | 1.13 MiB |
| ce900e7 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 44b7b59 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| c958e9d | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 48fa69f | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 92bcc8f | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| ae8cece | 24.14 KiB | 1.15 MiB | 1.13 MiB |
Previous results on branch: philprime/objc-wrapper-sdk-6342
Startup times
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5857eca | 1236.71 ms | 1264.36 ms | 27.64 ms |
| fc0a15a | 1220.29 ms | 1256.85 ms | 36.57 ms |
| 87bd6e0 | 1240.02 ms | 1246.75 ms | 6.73 ms |
| a1f5242 | 1222.28 ms | 1245.53 ms | 23.25 ms |
| 3d2e8a4 | 1235.17 ms | 1264.59 ms | 29.42 ms |
| 694c3ad | 1225.69 ms | 1250.48 ms | 24.79 ms |
| 7e3cf1f | 1208.80 ms | 1244.33 ms | 35.52 ms |
| 82293b9 | 1205.29 ms | 1241.32 ms | 36.03 ms |
| e55e60d | 1217.93 ms | 1251.80 ms | 33.86 ms |
| 31709d3 | 1223.00 ms | 1255.70 ms | 32.70 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 5857eca | 24.14 KiB | 1.13 MiB | 1.10 MiB |
| fc0a15a | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 87bd6e0 | 24.14 KiB | 1.13 MiB | 1.11 MiB |
| a1f5242 | 24.14 KiB | 1.12 MiB | 1.10 MiB |
| 3d2e8a4 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 694c3ad | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 7e3cf1f | 24.14 KiB | 1.12 MiB | 1.10 MiB |
| 82293b9 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| e55e60d | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 31709d3 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
The SentryObjC product depends on SentryCppHelper, but it was only defined in the binary targets section. Add it to the compile-from-source targets array so it's available when building SentryObjC from source. Fixes SPM package resolution error: target 'SentryCppHelper' referenced in product 'SentryObjC' could not be found Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add SentryObjCSDK.m implementation that forwards all method calls to SentrySDKInternal. This enables SentryObjC to work in projects with CLANG_ENABLE_MODULES=NO, where Swift's @objc bridging is unavailable. The wrapper provides a pure Objective-C implementation of the SentrySDK class, making all SDK methods accessible via #import <SentryObjC.h> without requiring modules or Swift bridging. Update iOS-ObjectiveCpp-NoModules sample to use the corrected import style (#import <SentryObjC.h> instead of <SentryObjC/SentryObjC.h>) and add module.modulemap for SPM module resolution. Refs #6342
Sentry Build Distribution
|
SentryCppHelper must remain in the initial targets array because the binary distribution products (Sentry, SentrySwiftUI, etc.) depend on it. Moving it to the compile-from-source section broke those binary products. The target can be safely referenced by both binary and source products.
7abca39 to
913cdfd
Compare
Sentry Build Distribution
|
itaybre
left a comment
There was a problem hiding this comment.
Almost LGTM, just some comments
|
|
||
| #import "AppDelegate.h" | ||
| #import <Sentry/Sentry.h> | ||
| #import <SentryObjC.h> |
There was a problem hiding this comment.
l: Just <SentryObjC.h> works?
I would expect it to be SentryObjC/SentryObjC.h but that may be the module...
There was a problem hiding this comment.
Both forms work, but for different reasons.
SentryObjC is a compile-from-source SPM target (like SentrySPM, not a pre-compiled framework like Sentry), so SPM adds its publicHeadersPath directory directly to the consumer's header search paths. That's why the flat #import <SentryObjC.h> resolves.
The framework-qualified #import <SentryObjC/SentryObjC.h> would also work and is more portable (e.g. if someone later consumes the pre-built XCFramework instead).
Happy to switch if you prefer.
| # This target is expected to FAIL until the pure ObjC SDK wrapper (#6342) | ||
| # is implemented. Use it to verify the fix. | ||
| # Builds the ObjC++ without-modules sample that uses SentryObjC (#6342). | ||
| # Uses #import <SentryObjC/SentryObjC.h> for ObjC++ without -fmodules. |
There was a problem hiding this comment.
l: So #import <SentryObjC/SentryObjC.h> or #import <SentryObjC.h>?
There was a problem hiding this comment.
Ah an inconsistency. See my other PR feedback comment:
#7598 (comment)
Happy to update afterwards
| private let levelNames = ["none", "debug", "info", "warning", "error", "fatal"] | ||
|
|
||
| extension SentryLevel: CustomStringConvertible { | ||
| extension SentryLevel: CustomStringConvertible { |
There was a problem hiding this comment.
m: @philprime there is a warning here:
extension declares a conformance of imported type 'SentryLevel' to imported protocol 'CustomStringConvertible'; this will not behave correctly if the owners of 'SentryHeaders' introduce this conformance in the future
There was a problem hiding this comment.
This is a SourceKit false positive. SentryLevel is defined in Sources/Sentry/Public/SentryLevel.h — same module as this Swift extension. The compiler confirms it: adding @retroactive fails with "'retroactive' attribute does not apply; 'SentryLevel' is declared in this module." The warning doesn't appear in actual builds (only in the IDE via SourceKit).
|
|
||
| - (void)setBeforeSendMetric:(SentryBeforeSendMetricCallback)block | ||
| { | ||
| objc_setAssociatedObject(self, kBeforeSendMetricKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC); |
There was a problem hiding this comment.
l: Will this break automatically if we rename SentryOptions?
There was a problem hiding this comment.
The SentryObjC/Public/SentryOptions.h is a hand-written ObjC header, and the actual class comes from Options.swift via @objc(SentryOptions).
If someone renamed just the @objc(...) name, this header would still compile but the runtime class wouldn't match — so it would be a runtime failure, not a compile error.
This is one of the major downsides of this split declaration style (manually maintained ObjC header backed by a Swift implementation), but the only other approach would be creating a SentryObjcOptions.h interface + implementation, which would duplicate quite some logic.
| iphoneos) arch_targets=( "arm64-apple-ios15.0" ) ;; | ||
| iphonesimulator) arch_targets=( "arm64-apple-ios15.0-simulator" "x86_64-apple-ios15.0-simulator" ) ;; | ||
| macosx) arch_targets=( "arm64-apple-macos10.14" "x86_64-apple-macos10.14" ) ;; | ||
| maccatalyst) arch_targets=( "arm64-apple-ios15.0-macabi" "x86_64-apple-ios15.0-macabi" ) | ||
| sysroot="$(xcrun --sdk macosx --show-sdk-path)" ;; | ||
| appletvos) arch_targets=( "arm64-apple-tvos15.0" ) ;; | ||
| appletvsimulator) arch_targets=( "arm64-apple-tvos15.0-simulator" "x86_64-apple-tvos15.0-simulator" ) ;; | ||
| watchos) arch_targets=( "arm64-apple-watchos8.0" "arm64_32-apple-watchos8.0" ) ;; | ||
| watchsimulator) arch_targets=( "arm64-apple-watchos8.0-simulator" "x86_64-apple-watchos8.0-simulator" ) ;; | ||
| xros) arch_targets=( "arm64-apple-xros1.0" ) ;; | ||
| xrsimulator) arch_targets=( "arm64-apple-xros1.0-simulator" ) ;; |
There was a problem hiding this comment.
m: What's the reason for these specific archs?
Will these need to be updated if we bump the minimum versions?
There was a problem hiding this comment.
I changed it so that it dynamically finds the relevant versions from what's configured in the Xcode build settings
|
|
||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| @implementation SentrySDK |
There was a problem hiding this comment.
m: I was wondering, wont this cause duplicate classes?
There was a problem hiding this comment.
Good catch! I assumed that the embedded Sentry.framework would be internal, but both export a class with the same ObjC runtime name:
$ nm SentryObjC.framework/SentryObjC | grep 'OBJC_CLASS.*SentrySDK' | grep -v 'Internal\|Log\|Only'
00000000000087a0 S _OBJC_CLASS_$_SentrySDK
00000000000082d0 s __OBJC_CLASS_RO_$_SentrySDK
$ nm SentryObjC.framework/Frameworks/Sentry.framework/Sentry | grep 'OBJC_CLASS.*SentrySDK' | grep -v 'Internal\|Log\|Only'
000000000022bfb8 S _OBJC_CLASS_$__TtC6Sentry9SentrySDK
SentryObjC.framework/SentryObjC:_OBJC_CLASS_$_SentrySDKis the ObjC class fromSentrySDK.mSentryObjC.framework/Frameworks/Sentry.framework/Sentry:_OBJC_CLASS_$__TtC6Sentry9SentrySDKis the Swift class, mangled symbol, but registers asSentrySDKin the ObjC runtime via@objc
Both resolve to the same ObjC runtime name SentrySDK, causing a duplicate class conflict at runtime. Interestingly it did not surface during testing, but it makes sense to fix this now.
Since SentryObjC is the new SDK, I'll rename its class to SentryObjcSDK to avoid the collision.
Use the setup-xcode composite action instead of calling ci-select-xcode.sh directly. Extract the inline arrange_slices bash from the workflow into scripts/ci-arrange-xcframework-slices.sh so it can be run locally for testing.
Avoids duplicate ObjC class registration when both SentryObjC.framework and its embedded Sentry.framework are loaded at runtime.
…r-sdk-6342 # Conflicts: # Sentry.xcodeproj/project.pbxproj
- Use --sdk named args instead of positional parameter - Derive deployment targets from Xcode project settings - Derive architectures from lipo -archs on built archives - Use mktemp for temporary files - Rename build-sentryobjc to build-framework-objc
- Align protocol replay conditional with Swift bridge guards (check SENTRY_NO_UI_FRAMEWORK) - Use atomic associated objects for thread safety - Guard empty deployment targets in standalone build - Add trap for temp dir cleanup on early exit
The SentryObjC scheme may not expose all platform deployment targets on CI. Use the main Sentry scheme which reliably has all settings. Also anchor awk patterns to avoid matching RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit de97037. Configure here.
Fix broken shell quoting in api-stability.yml echo statements where inner double quotes broke the outer string. Revert iOS-ObjectiveC sample dependency back to Sentry/Sentry since SentrySampleShared depends on the Sentry module, causing type redefinition errors when combined with SentryObjC.
The -sdk iphoneos flag causes xcodebuild -showBuildSettings to only return iOS-relevant settings on CI, omitting macOS/tvOS/ watchOS/xrOS deployment targets.
xcodebuild -showBuildSettings only returns deployment targets for the active SDK, not all platforms. Read directly from the canonical DeploymentTargets.xcconfig which has all five targets.
🚨 Detected changes in high risk code 🚨High-risk code can easily blow up and is hard to test. We had severe bugs in the past. Be extra careful when changing these files, and have an extra careful look at these:
|

📜 Description
Adds a pure Objective-C SDK wrapper (
SentryObjC) that enables Sentry usage in Objective-C++ projects withCLANG_ENABLE_MODULES=NO. This solves the long-standing issue where Swift SDK APIs are inaccessible without Clang modules, affecting React Native, Haxe, and other custom build systems.Read the develop-docs/SENTRY-OBJC.md architecture document first.
💡 Motivation and Context
Closes #6342
The Problem
Since SDK 8.54.0, the Swift SDK's public API requires Clang modules to be imported. When
CLANG_ENABLE_MODULES=NO:#import <Sentry/Sentry.h>only exposes Objective-C APIsSentrySDK,SentryOptions.sessionReplay, and the Metrics API are unavailableuse of undeclared identifier 'SentrySDK'This breaks projects that cannot enable modules, particularly:
Implementation Approach & Thought Process
Initial Considerations
Option 1: Make Swift SDK importable without modules
Option 2: Pure ObjC wrapper (chosen approach)
Architecture Decision
The wrapper uses a three-layer architecture:
Why this design?
See
develop-docs/SENTRY-OBJC.md📝 Checklist
🔗 Related Issues & Context