Mobile RUM - iOS SDK
The Mobile RUM SDK provides a customizable suite of tools to analyze and optimize the performance of iOS applications. Track network changes, detect crashes, identify slow or frozen frames and more. To see an example of how to deploy the Mobile RUM iOS SDK, navigate to our GitHub repository.
Prerequisites#
iOS 13+, macOS 10.15+, and tvOS 13+ are supported. Confirm the available SDKs with:
1xcodebuild -showsdksTip: Ensure your app targets HTTPS endpoints; iOS App Transport Security (ATS) blocks clear‑text HTTP by default.
Install & Instrument Your iOS Application#
1 Install Swift Package#
Add the package to your project using Swift Package Manager. This brings in the RUM runtime and public APIs used below. The latest release is 2.1.2. Package.swift (server‑side or multi‑package setups):
1.package(url: "https://github.com/middleware-labs/middleware-ios", from: "2.1.2"),Xcode UI: In Xcode, File → Add Packages…, paste the repository URL, select the version rule (exact or up to next major), and add the package to your app target.
CocoaPods is also supported:
1pod 'MiddlewareRum', '~> 2.1'MiddlewareRum is a static framework. If your Podfile uses use_frameworks!, pods that depend on it must also be linked statically.
2 Initialize Middleware iOS SDK#
Initialise the SDK as early as possible so the app starts and the first screen is captured. The example below shows a SwiftUI app initialising in App.init(). Provide your target, access token, and identity fields (serviceName, projectName, deploymentEnvironment). Add a stable release tag via globalAttributes(["app.version": …]) to correlate sessions and crashes with deployments.
1import SwiftUI
2import MiddlewareRum
3
4@main
5struct YourApp: App {
6 init() {
7 do {
8 try MiddlewareRumBuilder()
9 .globalAttributes(["app.version" : "1.0.0"])
10 .target("<target>")
11 .serviceName("Mobile-SDK-iOS")
12 .projectName("Mobile-SDK-iOS")
13 .rumAccessToken("<MW_API_KEY>")
14 .deploymentEnvironment("PROD")
15 .build()
16 } catch {
17 // Handle init errors (e.g., invalid config)
18 print("RUM init error: \(error)")
19 }
20 }
21
22 var body: some Scene {
23 WindowGroup { ContentView() }
24 }
25}UIKit AppDelegate example (for non‑SwiftUI projects):
1import UIKit
2import MiddlewareRum
3
4@main
5class AppDelegate: UIResponder, UIApplicationDelegate {
6 func application(_ application: UIApplication,
7 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
8 do {
9 try MiddlewareRumBuilder()
10 .globalAttributes(["app.version" : Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? ""])
11 .target("<target>")
12 .serviceName("MyApp")
13 .projectName("MyApp")
14 .rumAccessToken("<MW_API_KEY>")
15 .deploymentEnvironment("PROD")
16 .build()
17 } catch {
18 print("RUM init error: \(error)")
19 }
20 return true
21 }
22}Consent gating (optional): If you gate analytics on user consent, perform the MiddlewareRumBuilder() initialisation only after consent is granted. To disable just session recording (keeping metrics/logs), see Session Recording below.
Distributed Tracing#
End‑to‑end tracing links a RUM session to the backend traces it caused, so you can open a slow screen in the Session Explorer and see the server spans behind it.
It works by trace‑context propagation: the SDK creates a client span for each outgoing request and injects the W3C traceparent header. Your instrumented backend continues that same trace, and Middleware correlates the two by trace ID. For the full picture across RUM, traces, and logs, see Correlation with Data.
This is on by default and requires no code. URLSession is instrumented automatically, so any request made through it — directly, or through a library built on it such as Alamofire — is traced and propagates. Requests to the Middleware ingest endpoints are excluded.
The only remaining requirement is that your backend services are instrumented and accept W3C Trace Context.
Restricting which hosts receive trace headers#
By default every request carries trace headers. To keep your trace IDs off third‑party APIs, list the hosts that should receive them:
1try MiddlewareRumBuilder()
2 // ... other configuration
3 .tracePropagationTargets(["api\\.example\\.com", "checkout\\.example\\.com"])
4 .build()Each entry is a regular expression searched for anywhere in the request URL, so api.example.com matches https://api.example.com/orders. Requests to other hosts are still timed and still appear in the session — they just travel without trace headers. Passing an empty array disables propagation entirely, and patterns that fail to compile are ignored.
Verifying it works#
In the Session Explorer, open a network event and check that the backend trace resolves.
- Backend spans appear — correlation is working end to end.
- A trace ID is present but no backend spans — the request propagated correctly and the gap is server-side; confirm that service is instrumented and accepts W3C Trace Context.
- No trace at all — check that network monitoring has not been turned off with
.disableNetworkMonitoring(), and that the request is not going to an excluded ingest path.
Custom Configurations#
Logs#
Use logs to capture notable client‑side events and correlate them with sessions and replays. Prefer concise, structured messages; avoid secrets.
1MiddlewareRum.info("Some information")
2MiddlewareRum.debug("Some information")
3MiddlewareRum.trace("Some information")
4MiddlewareRum.warning("Some information")
5MiddlewareRum.error("Some information")
6MiddlewareRum.critical("Some information")Errors to Traces#
Promote important errors to traces so they’re visible in APM views and timelines.
1MiddlewareRum.addError("Error, unable to process")Exceptions#
Crashes are captured automatically; use exceptions for handled failures you still want to investigate.
1MiddlewareRum.addException(e: NSException(name: NSExceptionName(rawValue: "RuntimeException"),
2 reason: "I am a custom exception"))Set Your Screen Name#
Name key screens to make navigation and drill‑downs easier in dashboards (pair with Routing/URL where available).
1MiddlewareRum.setScreenName("WebView")Set Global Attributes#
Attach stable keys (release, plan, tenant) used for filtering and grouping. Avoid PII; prefer IDs or hashed values.
1MiddlewareRum.setGlobalAttributes(["some": "value"])WebView Instrumentation#
If your app embeds web content, integrate the WebView so Browser RUM and Mobile RUM can be correlated for hybrid journeys.
1MiddlewareRum.integrateWebViewWithBrowserRum(view: webView)Session Recording#
Session Replay captures what the user saw and did—use it to understand regressions and validate fixes. As of SDK 2.1, replay uses the next-generation recording pipeline (rrweb-based) and starts automatically when the SDK is initialised; recordings play back frame-accurately in the Middleware session player alongside your web sessions. Recordings are batched and uploaded efficiently.
- Maximum duration: 4 hours per session.
- Idle timeout: 15 minutes of inactivity ends the recording; activity after the timeout creates a new session.
- Default: Recording is enabled. To disable it at initialisation, call
.disableRecording()on the builder. To opt out of only the new pipeline (falling back to the legacy recorder), call.disableRecordingV3().
Tune capture rate and quality with RecordingOptions:
1try MiddlewareRumBuilder()
2 // ... target/token/etc.
3 .recordingOptions(RecordingOptions()
4 .setFrequency(.standard) // low | standard | high
5 .setQuality(.standard) // low | standard | high
6 .setMaskAllTextInputs(true) // default: true
7 .setMaskAllImages(true)) // default: true
8 .build()Disable recording example:
1try MiddlewareRumBuilder()
2 .globalAttributes(["customerId" : "123456"])
3 .target("<target>")
4 .serviceName("Mobile-SDK-iOS")
5 .projectName("Mobile-SDK-iOS")
6 .rumAccessToken("<MW_API_KEY>")
7 .deploymentEnvironment("PROD")
8 .disableRecording()
9 .build()Privacy#
Masking happens on‑device before upload. Mark views that may contain sensitive content (payments, OTPs, tokens) so they are blurred in all recordings. Password fields are masked automatically by default.
1// SwiftUI
2Text("Very important sensitive text").sensitive()
3
4// UIKit
5MiddlewareRum.addIgnoredView(view)You can also mark views declaratively via accessibility identifiers or labels: mw-no-capture always masks the view, mw-no-mask never masks it (and wins over global rules):
1cardNumberField.accessibilityIdentifier = "mw-no-capture"Secure text fields, and sensitive textContentType fields (passwords, credit cards, one-time codes), are always masked regardless of configuration. Review high‑risk screens regularly and apply masking to any additional fields that could contain secrets or PII.
Instrumentation Attributes#
Use the following builder methods to configure the SDK. These switches impact client‑side collection and do not require server changes.
| Method | Description |
|---|---|
.rumAccessToken(String) | Sets RUM account access token to authorize client to send telemetry to Middleware |
.target(String) | Sets target URL to the location you will send telemetry data to (e.g., your Middleware ingest URL) |
.serviceName(String) | Sets service name for your application |
.deploymentEnvironment(String) | Sets environment attribute on spans generated by instrumentation(e.g. PROD & DEV) |
.disableCrashReportingInstrumentation() | Disable crash reporting (which is enabled by default) |
.disableNetworkMonitoring() | Disable HTTP Instrumentation (which is enabled by default) |
.disableSlowRenderingDetection() | Disable slow/frozen frame renders (which is enabled by default) |
.slowFrameDetectionThresholdMs(Double) | Sets default polling for slow render detection. Default value is 16.7 milliseconds |
.frozenFrameDetectionThresholdMs(Double) | Sets default polling for frozen render detection. Default value is 700 milliseconds |
.tracePropagationTargets([String]) | Restricts which outbound request URLs carry traceparent. Matches every URL by default. See Distributed Tracing |
Troubleshooting#
Start here if data isn’t appearing or specific signals are missing.
- No data appears: Verify
rumAccessTokenandtargetare correct, and that initialisation runs once at app start. Check that your device has network connectivity. - Nothing from web content: Ensure you’ve called
integrateWebViewWithBrowserRum(view:)on the active WebView instance. - High slow/frozen frame counts: Reduce main‑thread work. A frame > 16.7 ms is considered slow; > 700 ms is frozen. Profile with Instruments (Time Profiler, Core Animation).
- Privacy concerns: Confirm sensitive views are annotated with
.sensitive()(SwiftUI) oraddIgnoredView(UIKit). Review masking after UI changes. - ATS blocked calls: The iOS ATS policy blocks non‑TLS endpoints; ensure your APIs and target use HTTPS. For dev‑only exceptions, add scoped ATS exceptions in
Info.plist. - No backend traces linked to a session: The request propagated but the backend did not continue the trace — confirm that service is instrumented and accepts W3C Trace Context. See Distributed Tracing.
- Trace headers reaching a third-party API: Narrow
.tracePropagationTargets([...])to your own domains.
Need assistance or want to learn more about Middleware? Get in touch with us via our Contact Us or join our Slack channel.
What did you think of this content?