Practical examples of debugging with Xcode for modern Apple apps

If you build apps for Apple platforms, you spend a lot of time in the debugger. Theory is nice, but real examples of debugging with Xcode are what actually save your release builds and your sanity. In this guide, we’ll walk through practical, real-world examples of examples of debugging with Xcode that mirror the bugs you actually hit: crashes only happening on physical devices, weird layout glitches, random memory spikes, and those mysterious hangs that never reproduce when your manager is watching. Along the way, we’ll look at how the Xcode debugger, Instruments, and runtime tools fit together. These examples of debugging with Xcode focus on iOS and macOS in 2024–2025, including Swift concurrency, SwiftUI issues, and performance problems on modern Apple Silicon devices. The goal is simple: when your app breaks, you’ll know which Xcode tools to reach for, how to interpret what they tell you, and how to turn vague bug reports into clear, fixable causes.
Written by
Jamie
Published
Updated

Real-world examples of debugging with Xcode in 2024–2025

Most tutorials stay abstract. Let’s skip that and walk through real examples of debugging with Xcode that match what you actually see in day‑to‑day development.

Imagine this as a tour through your bug tracker, but with Xcode’s tools turned up to 11.


Example of fixing a crash that only happens on device

You ship a TestFlight build. On your simulator? Everything’s fine. On real devices? Users report a crash when they tap a button that opens the camera.

You plug in an iPhone, run from Xcode, and reproduce the crash. The app dies without much context, but Xcode shows a backtrace in the debug navigator. The key move here is to:

  • Enable “All Objective‑C Exceptions” and “Swift Error Breakpoint” in the breakpoint navigator.
  • Re-run the app so Xcode stops at the line where the exception is thrown.

Suddenly the picture clears: the crash happens inside a call to AVCaptureDevice.default. On device, the user has denied camera permission, and your code force‑unwraps an optional.

This is one of the best examples of how a simple exception breakpoint turns a mysterious crash into a one-line fix: change

let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)!

into

guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
    // Show an error or prompt user to enable camera
    return
}

The pattern here repeats across many real examples of debugging with Xcode: let the debugger stop before the app dies, then inspect state instead of reverse‑engineering a crash log.


Examples of debugging layout glitches with Xcode’s view debugging

A classic example of debugging with Xcode: your view looks perfect in Interface Builder, but on a real iPhone 15 Pro everything is shifted, clipped, or just invisible.

You run the app, navigate to the broken screen, and hit Debug View Hierarchy. Xcode freezes the UI and shows every view, constraint, and frame in a 3D hierarchy.

In one case, a developer finds that a UILabel is off-screen. The view debugger shows:

  • The label has a frame of (x: -200, y: 40, width: 300, height: 20).
  • Auto Layout warnings are visible in the Issues pane about ambiguous constraints.

Zooming into constraints, you see a missing leading constraint on the label for compact width. On big simulators it “sort of works,” but on smaller devices the system resolves it in the wrong direction.

You add the missing constraint, re-run, and the layout stabilizes. This is a textbook example of how Xcode’s view debugger turns a vague “it looks wrong” into specific, fixable constraints.

For SwiftUI, the same approach applies, but you watch the runtime warnings in Xcode’s console and the Preview Canvas. SwiftUI now logs layout conflicts and performance hints, which are underrated examples of debugging with Xcode in the modern UI stack.


Examples include chasing memory leaks with Instruments

Performance questions are everywhere now that users expect 120 Hz smoothness and all‑day battery life. One of the best examples of debugging with Xcode in 2024–2025 is using Instruments to track down leaks and retain cycles.

You open Product → Profile and choose the Leaks template. While you navigate the app, Instruments flags a growing list of leaked objects: MyViewModel, NetworkManager, and some custom views.

You select MyViewModel and view its Retain Cycles. Instruments shows:

  • MyViewController strongly holds MyViewModel.
  • MyViewModel has a closure property onUpdate that strongly captures self.

That ring explains why your controller never deallocates. You switch to Xcode, change the closure to capture self weakly:

onUpdate = { [weak self] value in
    self?.updateUI(with: value)
}

Re-run the Leaks instrument, and the cycle disappears. This workflow—Profile, reproduce, inspect retain cycles, fix—is one of the clearest real examples of debugging with Xcode and Instruments working together.

If you want more background on memory and performance behavior on modern CPUs, Apple’s own developer documentation is still the gold standard: https://developer.apple.com/documentation.


Example of tracking down a random hang with the Xcode debugger

Another example of debugging with Xcode that comes up constantly: the app “freezes” at random. No crash, no log, just a spinning UI.

You attach Xcode to the running process (Debug → Attach to Process) and wait for the hang to occur. As soon as the UI stops responding, you hit Pause in Xcode.

The main thread’s backtrace shows it blocked on a DispatchSemaphore.wait() call, or stuck in a long‑running synchronous network call.

You switch to the Debug Navigator, inspect all threads, and see:

  • Thread 1 (main) is waiting on a semaphore.
  • Another background thread that should signal the semaphore is itself blocked on a main‑thread callback.

Classic deadlock.

You refactor to avoid waiting on the main thread, or switch to async/await:

Task { [weak self] in
    let result = try await apiClient.fetchData()
    await MainActor.run {
        self?.updateUI(with: result)
    }
}
``

This is a clean example of how Xcode’s thread view and pause/resume behavior are not just for crashes; they’re also for diagnosing deadlocks and long stalls.

---

## Examples of debugging Swift concurrency issues with Xcode

Swift concurrency introduced `async/await` and actors, but it also introduced a fresh class of bugs. In 2024–2025, some of the best examples of debugging with Xcode involve race conditions that only appear under load.

You notice data occasionally appears out of order in a chat app. There’s no crash, just inconsistent UI.

You enable **Thread Sanitizer** in your scheme (Diagnostics → Thread Sanitizer) and run again. Xcode immediately flags a data race on a shared `messages` array mutated from multiple tasks.

The fix: move the shared state into an `actor` and enforce access through async methods.

```swift
actor MessageStore {
    private var messages: [Message] = []

    func add(_ message: Message) {
        messages.append(message)
    }

    func all() -> [Message] {
        messages
    }
}

Re-running with Thread Sanitizer shows no more warnings. This is a sharp example of debugging with Xcode’s sanitizers: you trade mysterious, rare bugs for loud, actionable diagnostics.

For a more general introduction to race conditions and concurrency concepts, the material from universities like MIT and Stanford is still highly relevant, for example: https://web.mit.edu/6.031/www/sp22/classes/24-concurrency/.


Examples include using LLDB commands directly in Xcode

Most developers stay inside the graphical debugger, but some of the best examples of debugging with Xcode come from dropping into the LLDB console.

Picture a bug that only appears with a specific combination of model data. You don’t want to restart the entire app just to test different values.

While paused at a breakpoint, you open the LLDB console and run:

po viewModel.currentUser
expr viewModel.currentUser.name = "Test User"

You immediately see the UI update with the new name. From there you craft more expr commands to simulate server responses, toggle feature flags, or even call methods that are hard to reach through normal navigation.

This is an example of debugging with Xcode that feels almost like cheating: you treat the running app as a live REPL. Once you’re comfortable with po, expr, and bt (backtrace), you debug faster than you can recompile.

Apple’s LLDB guide is worth a read if you want to go deeper: https://developer.apple.com/library/archive/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/.


Example of catching logic bugs with unit test debugging

Not every bug happens in the UI. Some of the cleanest examples of debugging with Xcode happen inside unit tests.

You have a test that intermittently fails on CI but passes locally. You run the test in Xcode, set a breakpoint inside the function under test, and use the Test Navigator to repeatedly run just that test.

While stepping through, you notice that the test depends on the current date, and your logic handles daylight saving time incorrectly. By inspecting variables in the Variables View and using po in the console, you verify that the date math is off by one hour.

You refactor to inject a Clock protocol so tests can use a fixed time, re-run, and the flakiness disappears.

This is a quiet but important example of debugging with Xcode: you’re not just fixing tests, you’re building a debugging workflow that plays nicely with CI and long‑term maintenance.


Putting it together: patterns across the best examples

Across all these real examples of debugging with Xcode, a few patterns show up repeatedly:

  • Let Xcode stop before the app dies, using exception and error breakpoints.
  • Use the View Hierarchy and runtime warnings to debug layout instead of guessing.
  • Reach for Instruments when the bug involves time, memory, or energy.
  • Turn on sanitizers (Address, Thread) when behavior is weird but not obviously broken.
  • Treat LLDB as a live lab, not a last resort.

These patterns scale from hobby apps to large‑scale, regulated software. In fields like health or medical apps, where correctness and performance directly affect users’ well‑being, disciplined debugging is more than a productivity trick. If you’re working near that space, it’s worth reading up on broader safety and reliability practices from organizations like the U.S. National Library of Medicine: https://www.ncbi.nlm.nih.gov.

The more you practice on concrete examples of debugging with Xcode like the ones above, the faster you’ll be when the next production bug lands on your desk at 4:55 p.m. on a Friday.


FAQ: common questions and examples of debugging with Xcode

Q: What are some simple examples of debugging with Xcode for beginners?
A: Start with three basics: set a breakpoint on a line that should not be hit, use po in the debug console to inspect variables, and trigger a layout bug so you can explore the Debug View Hierarchy. Those small examples of using the debugger build the muscle memory you’ll need for tougher bugs.

Q: Can you give an example of using Instruments vs. the regular Xcode debugger?
A: Use the regular debugger when you want to inspect state at a specific moment—like why a variable is nil. Use Instruments when the problem involves behavior over time—like memory slowly climbing or scroll performance degrading after a few minutes. A classic example of debugging with Xcode is: debugger for “why did this crash now?”, Instruments for “why is this slow or leaky over time?”

Q: Are these examples of debugging with Xcode different for macOS vs. iOS?
A: The tools are basically the same: breakpoints, LLDB, Instruments, view debugging, sanitizers. The differences are mostly in the UI layers (AppKit vs. UIKit/SwiftUI) and hardware. On macOS you might care more about multi‑window state and keyboard shortcuts; on iOS you care more about battery and memory pressure. But the examples of debugging with Xcode described here apply to both.

Q: How do I practice without waiting for real bugs?
A: Intentionally break small sample apps. Introduce a retain cycle, a layout constraint conflict, or a data race, then use Xcode to track it down. Treat it like a workout: the more examples of debugging with Xcode you walk through when the stakes are low, the calmer you’ll be when production crashes start appearing in your logs.

Q: Where can I learn more about debugging techniques?
A: Apple’s WWDC sessions on debugging and Instruments are consistently good, and many university CS courses publish free material on testing and debugging. For broader software reliability topics, resources from institutions like Harvard and MIT can be helpful, for example: https://cs50.harvard.edu/x/.

Explore More Debugging Frameworks and Tools

Discover more examples and insights in this category.

View All Debugging Frameworks and Tools