SwiftQA Random

How do you detect memory leaks in production?

Why production detection is fundamentally different from dev-time detection

In development, you attach Instruments’ Leaks/Allocations tool or Xcode’s Memory Graph Debugger and directly inspect the object graph. In production, you have none of that — you’re inferring “something is leaking” from symptoms: rising memory usage, increased jetsam/OOM crash rates, degraded performance over a session — and then working backward to find the cause.

1. Memory usage telemetry — the primary signal

func logMemoryFootprint(context: String) {
    var info = task_vm_info_data_t()
    var count = mach_msg_type_number_t(MemoryLayout<task_vm_info_data_t>.size) / 4
    let result = withUnsafeMutablePointer(to: &info) {
        $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
            task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count)
        }
    }
    if result == KERN_SUCCESS {
        let usedMB = Double(info.phys_footprint) / 1024 / 1024
        Analytics.log("memory_footprint", properties: ["mb": usedMB, "context": context])
    }
}

Sending this at key checkpoints lets you build a picture of memory trend per flow across your whole user base.

2. Jetsam/OOM crash reports — the clearest downstream signal

On iOS, memory leaks that get bad enough result in the OS killing the app (a “jetsam” event) rather than a traditional crash. Crash reporting tools that distinguish OOM terminations from actual crashes are essential — a rising OOM rate is one of the strongest production leak signals.

MetricKit specifically is worth knowing by name — Apple’s MXMetricPayload/MXDiagnosticPayload APIs deliver aggregated memory and diagnostic data from real user devices, without needing a third-party SDK.

3. Session-length correlation

A leak often manifests as “memory grows the longer the app stays open,” so bucketing memory footprint (or OOM rate) against session duration or number of screen transitions is a useful analysis.

4. Feature-flag/version correlation to isolate the source

Once you see a memory/OOM signal, correlating it against app version and active feature flags narrows down when the leak was introduced — letting you go straight to the relevant diff rather than searching the whole app.

5. Automated detection in CI/QA before it ever reaches production — the actual prevention layer

func trackForMemoryLeaks(_ instance: AnyObject, file: StaticString = #filePath, line: UInt = #line) {
    addTeardownBlock { [weak instance] in
        XCTAssertNil(instance, "Instance should have been deallocated. Potential memory leak.", file: file, line: line)
    }
}

Also: Instruments’ Leaks tool run automated in CI against key user flows, and Xcode’s Memory Graph Debugger during manual QA passes on new features.

6. Common leak patterns worth explicitly checking for

  • Closures capturing self strongly in a context that outlives the intended scope.
  • Delegate properties declared strong/non-weak.
  • NotificationCenter observers never removed.
  • Timers retaining their target strongly.
  • Combine subscriptions/AnyCancellable never stored or cancelled.

7. What production detection genuinely cannot give you: root cause

Production telemetry tells you that something is leaking and roughly where, but essentially never tells you which specific object/closure/retain cycle is responsible — that last step almost always requires reproducing the suspected flow locally with Instruments once you have a strong enough hypothesis.

Interview-ready summary

SignalWhat it tells you
Memory footprint telemetry (custom logging)Trend per screen/flow across real user population
MetricKit / OOM-classified crash reportsStrongest signal something is leaking badly enough to kill the app
Session-length correlationWhether the issue is accumulation-over-time vs. a fixed cost
Version/feature-flag correlationNarrows down when the leak was introduced
CI-run Instruments / XCTest leak assertionsPrevention — catches leaks before they ship
Manual Memory Graph Debugger passesRoot-cause diagnosis, once you have a hypothesis

One-liner if pressed: “In production I’m working from symptoms, not direct object-graph inspection — memory footprint telemetry per screen, MetricKit/OOM-classified crash data, and correlating rising memory or termination rates against app version and session length to narrow down where and when a leak was introduced. That narrows the search, but actually finding the retain cycle almost always means reproducing the flow locally with Instruments’ Memory Graph Debugger — the real prevention is catching leaks earlier, via XCTest deallocation assertions and Instruments passes in CI before release.”