Which Instruments tools do you use, and for what problems?
TL;DR
Instruments is Xcode's profiling app. Open it with Product > Profile (Cmd+I). Pick a template for your problem:
| Problem | Instrument |
|---|---|
| Slow code, high CPU | Time Profiler |
| Memory growth | Allocations |
| Leaks and retain cycles | Leaks (plus Xcode's Memory Graph Debugger) |
| Scroll stutters | Animation Hitches |
| Slow launch | App Launch |
| SwiftUI updates | SwiftUI |
| Tasks and actors | Swift Concurrency |
| Network | Network |
| Battery | Power Profiler / Energy |
Always profile a Release build on a real device.
Time Profiler
It samples the call stacks many times per second. Functions that appear often are where time is spent.
Tips:
- Turn on Invert Call Tree to see the hottest functions first.
- Turn on Hide System Libraries to focus on your code.
- Select a time range around the slow moment, like a tap or a scroll.
- Look at the main thread first. Main thread work blocks the UI.
Allocations
Shows every allocation, its size and its call stack.
The best technique is generation analysis (Mark Generation):
- Open and close a screen once to warm up.
- Mark a generation.
- Open and close the screen again a few times, marking each time.
- Objects that keep growing in each generation are leaks or caches without limits.
Leaks and the Memory Graph Debugger
The Leaks instrument finds memory that nothing in the app can reach anymore. This includes many retain cycles. It cannot find abandoned memory: objects that are still referenced (by a cache, a singleton or a closure) but never used again. Use Allocations for that.
Xcode's Debug Memory Graph button is often faster for cycles:
final class ProfileViewController: UIViewController {
var onUpdate: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
onUpdate = { self.reload() } // strong cycle: self -> closure -> self
}
}The graph shows ProfileViewController still alive after you closed it, with the closure holding it. Fix with [weak self].
Animation Hitches
Records each frame and marks hitches. It tells you if the time was spent in your app (commit) or in the render server. Use it while you scroll.
SwiftUI instrument
Xcode 26 has a new SwiftUI instrument. It shows:
- Long view
bodyupdates. - How many times each view updated.
- A cause and effect graph: which state change made which view update.
Swift Concurrency instrument
Shows tasks, their states (running, suspended, waiting) and actors. Useful to find:
- Too many tasks created.
- A busy actor that makes others wait (actor contention).
- Work on the main actor that should run elsewhere.
Power and energy
The Power Profiler (Xcode 26) shows power use per subsystem: CPU, GPU, display, networking. You can also record a trace on the device itself, without Xcode, and look at it later.
Mark your own code with signposts
import os
let signposter = OSSignposter(subsystem: "com.example.feed", category: "Feed")
func loadFeed() async {
let state = signposter.beginInterval("LoadFeed")
defer { signposter.endInterval("LoadFeed", state) }
await fetchAndParse()
}Add the os_signpost / Points of Interest instrument. Your intervals appear on the timeline next to CPU and memory data.
Other useful instruments
- System Trace: threads, blocking, context switches.
- File Activity: disk reads and writes.
- Core Data: fetch counts and timing.
- Processor Trace (Xcode 26, on supported devices): records every function call, not samples.
Common follow-up questions
- Why not profile in the Simulator? It uses the Mac CPU and memory. Numbers are very different from a phone.
- Leaks shows nothing but memory grows. Why? It is probably abandoned memory, like an unbounded cache or an object a singleton still holds. Use generation analysis and the memory graph.
- What is a dSYM and why does it matter here? It maps addresses to function names. Without symbols you see only hex addresses.
- How do you track performance in production? MetricKit and the Xcode Organizer (launch, hangs, memory, battery).