A primary-source field guide

Instruments and Apple’s performance tools

Everything here is drawn from official Apple sessions and Apple documentation, with a link and a precision grade on every substantive claim. Start from the symptom you have, not the tool you know.

26 tools 69 routes in the chooser 51 sessions verified 6 sessions confirmed unreachable

00How to read this guide

Everything on this page comes from official Apple sessions and Apple documentation, read by three research crews who recorded a locator for each claim. This page adds no performance facts of its own. Where the evidence is thin, the gap is stated instead of filled.

Read the locator badges before you trust a number

Every substantive claim carries a citation chip linking to the exact Apple page. The dot on the chip tells you how precisely the claim is sourced, and the chips never promise more precision than the research recorded. A claim graded named section only or no locator never appears here with a timestamp.

WWDC26 268@3:39exact time Exact time. A timestamp read off the session page. Clicking it deep-links into the video at that second.
WWDC26 222Introduction chapternamed section only Named section only. A chapter or section name, not a timestamp. The link goes to the page.
WWDC19 423no locatorno locator No locator. The claim is attributed to that session, but no crew recorded where in it. Weakest grade on the page.
DocsImproving app responsivenessdoc Apple documentation, not a video. Several important facts exist only here.
demonstratedShown on screen in the session.
statedSaid aloud or on a slide, with no demo.
documentedFrom Apple documentation rather than the video.
inferredA research crew’s reasoning — explicitly not something a video shows.

Timestamps are accurate to roughly twenty seconds, not to the frame

The crews read their locators from Apple’s own chapter markers and transcript timecodes, but transcript text was grouped into roughly 20-second buckets while being read. A cited mark can therefore sit up to about twenty seconds before the sentence it supports. No timestamp here was rounded, computed or recalled — but none should be treated as frame-accurate either. Deep links use the form Apple’s own chapter links use (…/?time=<seconds>), which was confirmed on a live session page while building this guide.

What was verified while building this page

Every distinct Apple session URL cited across the three research reports — 54 of them — was re-fetched and its exact title, year and session number confirmed against the live page. Every timestamped citation was then checked against the set of transcript sentence start times and chapter marks actually present on that page: 385 of 386 distinct timestamped citations are exact page marks. The one exception was downgraded rather than repaired, and is named in Coverage boundaries. The six sessions the crews reported as unreachable were independently re-confirmed as unreachable.

This page renders 886 exact-time citations, 13 named-section citations, 8 with no locator, and 108 Apple documentation references.

01Approaching a performance question on macOS or iOS

Before any tool: what to measure, what build to measure it on, whether the Simulator will lie to you, and the difference between a signal from the field and a trace on your desk.

Schematic The triage question that organises everything below — drawn from the WWDC26 session’s own diagnostic flow. Not a screenshot. WWDC26 268@1:46exact timeWWDC26 268@2:33exact time
A symptom you can reproduce Time Profiler orient first CPU high CPU idle Code bottleneck — optimize, or offload Diffuse cost → Top Functions 100% CPU + async → actor congestion Blocked: file I/O, a lock, or IPC → System Trace: thread states, Narrative

Before you open Instruments

  • Reproduce it by hand and know the gesture. Every modern demo narrows the trace to one interval; you cannot narrow to an interval you cannot trigger on demand.
  • Turn on the free checks first. The Thread Performance Checker (scheme › Diagnostics) flags priority inversions and non-UI work on the main thread “while debugging your app without actively tracing it” — no trace, no recompilation. And the Energy Gauge in the Debug navigator answers the cheapest question there is: while the app is idle and waiting for the user, CPU should be at or near zero. WWDC22 10082@3:55exact timeWWDC21 10181@4:50exact time
  • Add a signpost around the operation you are about to tune, before you start. It becomes the inspection range, the comparison window, and the thing that makes a before/after credible. The WWDC26 session visibly loses time to hangs it cannot contextualise because no signpost covers them. WWDC26 268@26:14exact timeWWDC26 268@16:19exact time
  • Measure before you optimise, and instrument for the field. Apple’s own engineers name insufficient telemetry and instrumentation as the most common power mistake they see — teams over-index on one customer report while field analytics show a bigger problem. The second most common is not accounting for app state: a database write that looks cheap at your desk can dominate on a user’s much larger dataset. WWDC26 8003@12:27exact timeWWDC26 8003@13:17exact timeWWDC26 8003@13:41exact time

Release build, always

Device or Simulator? There are three different answers

A field signal and a local trace answer different questions

  • Field data (Organizer, MetricKit) is the only thing that can tell you the version you shipped is worse than the last one, for real users on real devices. It is collected from consented devices and aggregated by Apple, requiring no app changes. WWDC21 10181@6:33exact time
  • But field data tells you that and where, rarely why. An aggregated stack-trace signature points at code; reproducing it still needs a local trace. The WWDC21 demo does exactly this — Organizer finds the disk-write signature, then Instruments’ File Activity diagnoses and validates the fix. WWDC21 10087@11:13exact time
  • Within field data, metrics and diagnostics are different instruments. Metrics tell you an area is getting worse; only diagnostics carry the backtrace that identifies a code path. Diagnostics also arrive immediately while metrics are daily — confusing the two cadences is a common way to misjudge whether a fix landed. WWDC20 10081@2:11exact time
  • The desk structurally cannot reproduce some problems. A synchronous network request showed no delay “at my desk with Xcode and a strong network connection” and hung for over three seconds in the field. A location-driven power bug was invisible to a stationary developer and obvious to a commuting colleague. Neither was a measurement error. WWDC22 10082@11:33exact timeWWDC25 226@12:59exact time
  • Apple’s own tiering of the four altitudes: the Xcode gauges for instant feedback while coding, Instruments for deep dives, XCTest to catch issues early, and Organizer / MetricKit / the App Store Connect API after shipping. WWDC25 226@18:35exact time

Measuring the measurement

  • Recording changes what you are measuring. MallocStackLogging and Allocations track live data and cost memory and CPU; Leaks, VM Tracker and memory graphs are snapshot-based and suspend the target, which can visibly stutter or hang the app. The WWDC24 demo’s own trace ran “a little slower than last time, but that’s expected”. WWDC24 10173@32:11exact timeWWDC24 10173@6:40exact time
  • Immediate (streaming) mode is the expensive recording mode. It raises signpost cost by roughly an order of magnitude versus a release build not streaming. If signposts start appearing in your own profiles, suspect the recording mode before suspecting your code; deferred and last-few-seconds modes are the stated mitigation. WWDC19 414@7:05exact timeWWDC19 414@7:27exact time
  • Profiling overhead distorts phase durations — the WWDC19 launch trace shows dyld at 6 ms CPU against 149 ms wall clock. Compare like with like. No locator was recorded for this observation. WWDC19 423no locatorno locator
  • One run of a power measurement proves nothing. Thermals, device state, system pressure and your app’s own data volume all move the numbers, so Apple recommends capturing multiple runs of each approach under varying conditions and averaging. WWDC25 226@17:44exact timeWWDC25 226@18:10exact time

02Choose the tool

Start from the symptom you actually have. Each route names the tool, one line on why it fits, and one line ruling out the neighbouring tool people reach for instead. Filtering is a convenience — the full table reads in order without it, and prints in full.

Symptom to tool, with the neighbouring tool ruled out
The symptom or questionReach forWhy this, and not the neighbour
I don’t know what’s wrong yet.Freezes & stallsTime Profiler

It is the stated first step; it orients you before you commit to a specialized tool.

Not this — Not System Trace, which buries you in kernel detail before you know where to look.

WWDC26 268@1:53exact time
The app froze for a moment during a tap or button press.Freezes & stallsHangs instrument

Hangs are about discrete interactions and the 250 ms responsiveness threshold.

Not this — Not the Hitches instrument, which is for continuous motion.

WWDC22 10082@1:35exact time
It hangs and the main thread is near 100% CPU.Freezes & stallsTime Profiler call tree → Top Functions

This is a code bottleneck; either optimize the algorithm or offload the work.

Not this — Not System Trace: nothing is blocked.

WWDC26 268@2:05exact timeWWDC26 268@2:18exact time
It hangs and the main thread is near 0% CPU.Freezes & stallsSystem Trace (Thread States + Narrative)

The thread is off-CPU waiting on I/O, a lock, or IPC.

Not this — Not Time Profiler, whose call tree will be nearly empty and tell you nothing — it is structurally blind to off-CPU time.

WWDC26 268@2:33exact timeWWDC26 268@2:54exact time
It hangs, main thread is ~100% CPU, and the codebase is async.Freezes & stallsSwift Concurrency / Swift executors

This is actor congestion from inherited isolation, and the fix is relocating the work, not making it faster.

Not this — Not plain Time Profiler, which shows the busy stacks but not why they are on the main actor.

WWDC26 268@25:51exact timeWWDC23 10248@32:28exact time
Which thread is holding my main thread up?Freezes & stallsSystem Trace → “made runnable by”

It names the thread that released the lock.

Not this — Not the Hangs instrument, which tells you that you hung, not who caused it.

WWDC16 411@17:01exact time
I suspect a priority inversion or non-UI work on the main thread.Freezes & stallsThread Performance Checker

It finds both without any tracing at all, so it costs nothing to leave on.

Not this — Not Instruments, which is the heavier follow-up once it flags something.

WWDC22 10082@4:49exact time
It’s fine at my desk but users complain.Freezes & stallsOn-device hang detection (iOS)

Settings › Developer on a TestFlight build, under real network conditions.

Not this — Not a desk repro — the desk environment structurally cannot reproduce it.

WWDC22 10082@11:33exact time
Scrolling stutters but the app never freezes.Stutter & scrollingAnimation Hitches template / Hitches instrument

A missed frame deadline in the render loop, not an unresponsive main thread.

Not this — Not the Hangs instrument — the deciding question is continuous motion versus discrete interaction.

Tech Talks 10855@1:33exact time
Scrolling stutters and no individual view body is slow.Stutter & scrollingLazy-stack structure review

The cost is subview resolution, retention and prefetch loss.

Not this — Not the Long View Body Updates lane — none of those costs appears as a long body.

WWDC26 321@10:34exact time
My main thread looks fine and it still stutters.Stutter & scrollingRender-phase analysis (layer tree, view debugger)

A render hitch happens in the render server, on your layer tree, in another process entirely.

Not this — Not a main-thread call tree — it will look innocent.

Tech Talks 10855@3:57exact timeTech Talks 10857@6:43exact time
Cells resize mid-list and scrolling degrades.Stutter & scrollingConstant cell sizes

When something mid-list resizes, everything below moves, forcing recalculation of what is on screen and defeating SwiftUI’s caching.

Not this — Not a profiler finding — this is structural, and the panel answered it as such.

WWDC26 8003@39:13exact timeWWDC26 8003@39:37exact time
Which code burns the most CPU? (throughput, not responsiveness)CPU & throughputCPU Profiler

It samples per-CPU by clock frequency and avoids Time Profiler’s aliasing and its bias against faster-running cores.

Not this — Not Time Profiler — Apple explicitly prefers CPU Profiler for CPU optimization since WWDC25.

WWDC25 308@10:12exact time
How work is distributed over time / which threads are active at once.CPU & throughputTime Profiler

This is Time Profiler’s stated remaining strength.

Not this — Not CPU Profiler, which is the better choice for “which code burns CPU” but not for this question.

WWDC25 308@9:31exact time
Sampling isn’t resolving it; I need every call.CPU & throughputProcessor Trace

A complete user-space instruction trace at ~1% overhead, including compiler-generated ARC and C++ code. M4 / A18-class hardware, a few seconds.

Not this — Not CPU Profiler, which estimates from samples.

WWDC25 308@14:09exact time
My hot loop is as tight as I can make it — what is the CPU stalling on?CPU & throughputCPU Counters with preset modes

It converts hardware counters into bottleneck percentages.

Not this — Not Processor Trace, which shows what ran, not which microarchitectural resource stalled.

WWDC25 308@22:35exact time
No single function is hot but it’s still slow.CPU & throughputTop Functions view

It surfaces diffuse cost summed across many code paths.

Not this — Not a call tree or flame graph, which hide exactly this shape.

WWDC26 268@11:10exact time
How long does this specific function take?CPU & throughputos_signpost / OSSignposter interval

Time Profiler gives sample counts, which cannot distinguish one slow call from many fast ones.

Not this — Not the call tree’s weight column — it is a statistical estimate, not a stopwatch.

WWDC23 10248@16:50exact time
My call tree is all system frames or unreadable names.CPU & throughputSymbolication and debug info

A missing dSYM makes every other tool in this list useless.

Not this — Not a tooling bug — fix the debug information first.

WWDC21 10211@0:34exact time
CPU is high while the UI is idle.SwiftUI updatesSwiftUI instrument → Update Groups lane

It tells you in one glance whether SwiftUI is even involved.

Not this — If the lane is empty it is not a SwiftUI problem — go to Time Profiler or Hangs/Hitches instead.

WWDC25 306@3:00exact time
This view redraws constantly and I don’t know which input causes it.SwiftUI updatesCause & Effect graph

It attributes the update to a gesture, state write or environment change.

Not this — Prefer it over _printChanges when you do not yet know which view to suspect; a raw backtrace is useless here because the frames are system frames.

WWDC25 306@25:14exact timeWWDC25 306@20:15exact time
I know which view, I just want the reason, now.SwiftUI updatesSelf._printChanges()

Inline and instant, no trace — and it can be invoked from LLDB without editing the file.

Not this — Prefer it over the instrument when you already have a hypothesis and a fast reproduction.

WWDC23 10160@7:46exact time
Is this view re-evaluating at all?SwiftUI updatesRandom background or border colour

No API, no tooling, visible at a glance during resize or scroll — the SwiftUI team’s own habit.

Not this — Not an instrument run: this answers where and how often, not how expensive.

WWDC26 8006@30:18exact time
A formatter, date or number call might be the cost.SwiftUI updatesSwiftUI instrument → Set Inspection Range and Zoom → Time Profiler

The handoff is the point — the SwiftUI instrument alone does not show you the function.

Not this — Not the SwiftUI lanes by themselves, which stop at “this body was long”.

WWDC25 306@7:19exact timeWWDC25 306@8:05exact time
Too many layers are compositing and the screen is heavy.SwiftUI updatesdrawingGroup()

It flattens the rendered layers onto one, and can help even when the same number of SwiftUI views update.

Not this — Not compositingGroup(), which is about applying an effect to a combined result, not performance.

WWDC26 8006@57:29exact timeWWDC26 8006@56:26exact time
Launch feels slow.LaunchInstruments App Launch template

The only tool that attributes time to launch phases and shows thread state, which is what exposes blocking.

Not this — Not Time Profiler alone — it will not show the main thread waiting.

DocsReducing your app's launch timedoc
I suspect pre-main() cost from static initializers.Launchdyld Activity instrument

It specifically measures time spent running static initializers — a direct measurement rather than inference from phase durations.

Not this — Not the launch-phase breakdown, which tells you dyld was slow but not which initializer.

DocsReducing your app's launch timedoc
Launch regressed and I don’t know when.LaunchXCTest launch test with a baseline

The free UI-test launch measurement fails the build on regression.

Not this — Not the Organizer, which only tells you after you ship.

WWDC19 417@14:18exact time
Memory climbs and never comes back down (stair-step).MemoryAllocations + Mark Generation, then the Memory Graph Debugger

Generations isolate which interval grew; the graph answers why it is still alive.

Not this — Not Leaks — it will not find it if the memory is reachable.

WWDC24 10173@14:01exact timeWWDC24 10173@18:04exact time
Memory spikes and recovers (sawtooth).MemoryAllocations, Lifespan = Created & Destroyed, call-tree view

You want the churn’s backtrace, not a snapshot.

Not this — Not a memory graph taken after the spike — it shows nothing, because the memory is already gone.

WWDC24 10173@9:06exact time
Huge transient spike inside a loop, autorelease-pool content visible.MemoryNested autoreleasepool in the loop body

The fix is scope, not allocation count.

Not this — Not an algorithmic change — the allocations are fine, their lifetime is not.

WWDC24 10173@11:58exact time
Is this object leaked or just retained?MemoryMemory Graph Debugger leak filter, then reachability reasoning

Leaked means unreachable; abandoned means reachable-but-useless.

Not this — Not a tool at all for the second case — abandoned memory needs human judgement.

WWDC24 10173@20:26exact time
Leak count fluctuates run to run.MemoryDo not chase it

It is conservative-reference noise — the tool is telling you about its own uncertainty, not about your code.

Not this — Not a real regression signal; a program leaking five objects may legitimately report five, then four.

WWDC24 10173@25:15exact time
Crash from EXC_RESOURCE.MemoryXcode pauses the app — open the Memory Graph Debugger immediately, then vmmap --summary

You want the footprint breakdown at the moment of death.

Not this — Not a fresh run, which will not be in the same state.

WWDC18 416@10:11exact time
Regression between two builds, with a memgraph from each.Memoryheap -diffFrom

It is the only tool that answers “what is in B that was not in A” directly.

Not this — Not vmmap, which sizes regions but does not diff object types.

WWDC21 10180@16:35exact time
Where is the memory, if not the heap?Memoryvmmap --summary on a memgraph, then VM Tracker

vmmap names the VM region (CG image, IOSurface, mapped file).

Not this — Not Allocations, which only sees the heap plus anonymous VM.

WWDC18 416@17:30exact time
Footprint high but heap small.MemoryVM Tracker / footprint

Allocations do not equal footprint — this is the explicit reason VM Tracker exists.

Not this — Not Allocations totals, which answer “what churned”, not “what am I charged for”.

WWDC22 10106@13:19exact time
Footprint high, heap total looks fine, long-running process.Memoryvmmap --summary, bottom per-zone table, % FRAG

Fragmentation is a footprint multiplier invisible to every other view.

Not this — Not a leak hunt — nothing is leaked, the pages are just badly packed.

WWDC21 10180@24:34exact time
Memory grows on a Mac or Simulator process with no Xcode session.Memoryleaks attached directly

It is the one CLI path that does not need a captured graph.

Not this — Not the Memory Graph Debugger, which needs an Xcode debug session.

WWDC24 10173@4:43exact time
Image-heavy screen uses far more memory than the file sizes suggest.MemoryArithmetic, then ImageIO downsampling

Image memory tracks dimensions, not file size: width × height × bytes-per-pixel.

Not this — Not a tool — the gauge will confirm it, but the diagnosis is dimensional.

WWDC18 416@17:51exact timeWWDC18 416@22:35exact time
Xcode shows high energy impact but I don’t know which subsystem.Battery & thermalPower Profiler

Its whole purpose is to rank CPU / GPU / display / networking impact so you know where to aim.

Not this — Not the Energy Gauge, which flags “high” without localising it.

WWDC25 226@1:54exact time
Battery complaint I cannot reproduce at my desk.Battery & thermalOn-device Performance Trace in Power Profiler mode

A tethered session cannot see commuting, CarPlay, outdoors or hours-long background behaviour — and tethering also zeroes the system power lane.

Not this — Not borrowing the device: have the colleague capture and share the trace file.

WWDC25 226@10:31exact time
Power Profiler points at CPU.Battery & thermalHand off to CPU Profiler / Time Profiler

Power is the prioritiser; the profiler is the locator.

Not this — Not more power profiling — you already know the subsystem.

DocsMeasuring your app's power use with Po… § Make changes to reduce power usagedoc
Power Profiler points at networking.Battery & thermalHTTP Traffic + Network Connections

This pairing is documented; reduce connection frequency or bytes.

Not this — Not a CPU profile — the cost is radio time, not compute.

DocsMeasuring your app's power use with Po… § Make changes to reduce power usagedoc
Power Profiler points at display.Battery & thermalReduce average pixel luminance / support dark appearance

On OLED this is a direct, measurable lever.

Not this — Not a code optimisation — the cost is the pixels you are lighting.

WWDC19 417@7:31exact time
Choosing between two implementations.Battery & thermalMultiple Power Profiler runs of each, averaged

A single comparison is dominated by thermal and device state.

Not this — Not one run each — Apple explicitly recommends averaging across varying conditions.

WWDC25 226@17:44exact time
Is my idle app really idle?Battery & thermalEnergy Gauge in the Debug navigator

The >20%-CPU and should-be-near-zero-when-idle heuristics are the cheapest check available.

Not this — Not Instruments — this needs no trace, no baseline and no instrumentation.

WWDC21 10181@4:30exact time
Battery reported high, but the foreground screen looks innocent.Battery & thermalPower Profiler, untethered

SwiftUI and UIKit compute should show up as foreground energy, so background drain points at scheduled background tasks or location use.

Not this — Not the visible screen — reading the gauge as a verdict on it is the specific mistake.

WWDC26 8003@18:37exact timeWWDC26 8003@18:43exact time
App degrades outdoors or in sunlight.Battery & thermalThermal-state notifications + Xcode device conditions

You cannot reliably test thermal defences by warming a device; condition inducers make the device report the state safely.

Not this — Not a dummy CPU load — the session names that as the bad practice this replaces.

WWDC19 422@24:30exact timeWWDC19 422@24:52exact time
Screen loads slowly, many requests.NetworkHTTP Traffic, HTTP Transactions by Connection view

The staircase of blocked states is only visible grouped by connection.

Not this — Not the Tasks view, which hides head-of-line blocking.

WWDC21 10212@12:18exact time
A request appears not to reach the server.NetworkHTTP Transactions by Connection — look for Local Cache

A missing “waiting for response” state means no server was involved.

Not this — Not a packet capture, which cannot see a cache hit at all.

WWDC21 10212@23:56exact timeWWDC21 10212@24:51exact time
Auth works once then re-prompts.NetworkInterval labels (cookie icon) + transaction extended detail

The icons narrow it in seconds; the header view proves it — often a server-side expiry, not your networking code.

Not this — Not your client code first — absence of the cookie icon means the client correctly declined to send.

WWDC21 10212@20:33exact time
What is this third-party SDK actually sending?NetworkHTTP Traffic, sessions grouped by process; read bodies and resume backtraces

This is the audit case the instrument was built for — the trace itself becomes the bug report.

Not this — Not a packet capture, which cannot attribute traffic to a process or show you the resume backtrace.

WWDC21 10212@29:28exact time
I need to hand networking evidence to someone without Instruments.Networkxctrace export to HAR

Portable and scriptable.

Not this — At the cost of URLSession structure and backtraces, which HAR does not carry.

WWDC21 10212@31:55exact timeWWDC21 10212@32:42exact time
I need to test behaviour on a bad network.NetworkXcode device conditions or Network Link Conditioner

Remembering it is a ceiling, it is system-wide, and the status bar will not reflect it.

Not this — Not a real bad network you cannot reproduce on demand.

WWDC19 422@13:05exact timeWWDC19 422@13:28exact time
I suspect excessive writes.DiskFile Activity template, suggestions track first

It is the only tool that names known anti-patterns instead of making you count syscalls.

Not this — Not counting syscalls by hand — and remember zero suggestions is not zero I/O.

WWDC19 419@28:04exact timeWWDC19 419@32:32exact time
Did that write actually hit the device?DiskCompare file system activity (logical) with disk usage (physical)

The logical/physical gap is the signal.

Not this — Not either track alone — both are misleading in isolation.

WWDC19 419@33:16exact time
Shipped app is writing too much.DiskOrganizer disk-writes reports → top signatures → insights, then validate with File Activity

The field tells you what and how much; the instrument tells you whether your fix worked.

Not this — Not File Activity alone, which cannot see your users; and no report may just mean you did not cross 1 GB / 24 h.

WWDC21 10087@8:50exact timeWWDC21 10087@11:13exact timeWWDC21 10181@14:50exact time
Deciding between fsync, F_FULLFSYNC and F_BARRIERFSYNC.DiskDecide from the requirement, not the tool

Durability versus ordering. If ordering, use F_BARRIERFSYNC.

Not this — Not F_FULLFSYNC for ordering — it flushes the whole disk cache, punishing the system.

WWDC19 419@13:17exact timeWWDC19 419@12:09exact time
Storing small, frequently-changing state in a plist.DiskStop; use Core Data or SQLite with WAL

Whole-file replacement plus an fsync per write is the documented anti-pattern.

Not this — Not “it’s only one write” — the demo measured 12 I/O operations for roughly four lines of code.

WWDC19 419@14:02exact timeWWDC19 419@14:23exact time
I want to prevent disk regressions in CI.DiskXCTStorageMetric with a baseline

The only disk-specific gating mechanism named in this material.

Not this — Not a manual re-trace each release.

WWDC21 10181@14:01exact time
Is my shipped version worse than the last one?Field & regressionsXcode Organizer Insights

The only source of real-user, real-device, version-over-version data.

Not this — Not a local trace, which cannot see your users.

WWDC21 10181@6:33exact time
Organizer says there’s a hang but not where.Field & regressionsMetricKit diagnostics (or Organizer’s Generate Recommendations)

Diagnostics carry call-stack trees.

Not this — Not metrics, which never identify a code path.

WWDC20 10081@2:11exact timeWWDC26 258@20:32exact time
I need field data segmented by what the user was doing.Field & regressionsMetricKit + StateReporting

The Organizer cannot segment by your app’s own states.

Not this — Not an Organizer metric filter, which segments by device and OS, not by your flows.

WWDC26 8003@22:47exact timeWWDC26 222@10:03exact time
I need to stop this regressing again.Field & regressionsXCTest performance test with a baseline, or Thread Performance Checker set to fail tests

The only two gates in this material.

Not this — Not a dashboard you have to remember to check.

WWDC19 417@15:49exact timeDocsDiagnosing performance issues earlydoc
Something feels slow but I have no hypothesis yet.Method & proofXcode debug gauges

Zero setup, running already; use the idle-should-be-near-zero test.

Not this — Not the Organizer, which only reflects shipped versions, and not Instruments yet, which needs a hypothesis.

WWDC21 10181@4:50exact time
Which of my app’s operations was running when things went bad?Method & proofPoints of Interest track

Signpost with the points-of-interest category — the category is what auto-surfaces your data.

Not this — Not the system tracks: the Points of Interest track is empty until you instrument it.

WWDC26 268@6:09exact timeWWDC16 411@3:37exact time
I need to prove my fix actually helped.Method & proofRun Comparisons, filtered to the same signpost interval in both runs

Red/green per-function deltas instead of eyeballing two traces. New at WWDC26.

Not this — Not two traces side by side, and not two unfiltered runs — the comparison needs identical intervals to be honest.

WWDC26 268@13:30exact timeWWDC26 268@14:08exact time
I want to profile exactly one workload with nothing else in the trace.Method & proofLaunch from the test navigator’s per-test Profile item

The recording then contains your workload and almost nothing else.

Not this — Not a full app launch followed by manual navigation, which fills the trace with noise.

WWDC25 308@10:51exact time

03CPU, time & responsiveness

Freezes, stutters, hot code and blocked threads. Lead with the triage question: during the symptom, is the CPU busy or idle?

Schematic The five phases of the render loop, in the order the Tech Talk demonstrates them. A commit hitch is yours; a render hitch happens in the render server, on your layer tree. Not a screenshot. Tech Talks 10855@3:15exact time
your app render server display event commit render prepare render execute display commit hitch render hitch
Schematic What a thread-state band means. Only running is work; runnable is queueing time, and blocked is waiting on a resource. Time Profiler samples only the running stretches. Not a screenshot, and colours in your own trace should be read from its legend, not from here. WWDC19 423@23:21exact timeWWDC26 268@21:18exact time
running blocked runnable preempt running one thread, left to right in time Time Profiler sees only these ← System Trace is what explains this stretch: what it waited on, and who made it runnable →

Instruments, the app itself

CPU, time & responsiveness

Getting a recording you can trust, and driving the UI so it tells you something.

The question it answers

I have a performance symptom and an app. How do I get a trustworthy recording in front of me, and how do I drive the UI so the recording tells me something?

What the sessions show and say20
  • demonstratedProfiling starts from Product › Profile in Xcode, which builds the app for release and launches Instruments already targeting it. WWDC22 10082@6:28exact time
  • statedApple states the reason outright: “A debug build trades off runtime performance for debug ability, so profiling data from debug builds can be misleading. Profiling a release build is crucial to get the most actionable data.” WWDC26 268@3:39exact time
  • demonstratedInstruments then presents the template chooser. WWDC23 picks Time Profiler as the default starting point “if you don’t yet know” where the problem is; WWDC26 picks the Swift Concurrency template because the codebase uses Swift concurrency, noting the Time Profiler instrument is still available inside it. WWDC23 10248@9:55exact timeWWDC26 268@3:43exact time
  • statedEverything in the window is a trace document; Instruments can save and reopen them, so old recordings stay explorable. WWDC19 411@8:07exact time
  • statedA custom template you configure can be saved and reappears under the Custom tab of the template chooser later. WWDC16 411@10:59exact time
  • demonstratedTarget selection is a distinct area of the window; switching it to “All Processes” is demonstrated, and when Xcode launched the session the target area is already populated. WWDC19 411@9:31exact time
  • statedRecording modes. Immediate mode is the default and visualizes data in real time. WWDC18 405@26:57exact time
  • statedWindowed mode (also called Last Few Seconds mode) keeps only roughly the last five seconds; System Trace ships with it on by default because the data volume is otherwise enormous. WWDC16 411@2:52exact timeWWDC19 411@5:26exact time
  • demonstratedDeferred mode is selected in the recorder settings for lower overhead; the default immediate mode “can be useful to confirm your interactions with an app are captured.” WWDC25 308@11:13exact time
  • demonstratedNarrowing to a time range is the pivotal move in every modern demo. Three demonstrated gestures: triple-click an interval to create a time filter; secondary-click › Set Inspection Range, or hold Option for Set Inspection Range and Zoom; and control-click a row › Set Time Filter in the older System Trace idiom. WWDC22 10082@6:58exact timeWWDC23 10248@12:40exact timeWWDC16 411@14:10exact time
  • demonstratedCall tree shaping. The Call Tree button in the bottom bar exposes the Hide System Libraries checkbox, which “filter[s] out all functions from the system libraries” and makes your own code easier to focus on. WWDC23 10248@13:50exact time
  • demonstratedThe older, richer call tree popover plus context-menu operations: focus by context, Charge <symbol> to callers to remove a subtree’s content, and call tree constraints (e.g. a minimum sample count of 20) to suppress noise. This is a 2018 UI. WWDC18 407@19:43exact timeWWDC18 407@21:20exact timeWWDC18 407@22:31exact time
  • demonstratedThree detail views, one segmented control — the outline call tree (“great when looking at sample counts”), the flame graph (easier to scan and spot issues visually), and Top Functions — all reached from “the segmented control in the bar above the call tree”. WWDC26 268@10:36exact timeWWDC26 268@11:15exact time
  • demonstratedTop Functions lists functions by self weight on the left, with a flame graph of all code paths calling into the selected function on the right. It is the tool for “no single obvious issue… these different codepaths sum together to be costly enough to cause hangs.” WWDC26 268@11:25exact time
  • demonstratedRun Comparisons (new at WWDC26). Instruments compares profiling data across runs in one document, matching each function from the baseline run to the new version, computing a delta per node and sorting by performance difference: red = regression, green = improvement, with regressions sorted to the top by default. Comparisons appear as a tab in the sidebar, you can create several, and they are saved into the document for collaboration. WWDC26 268@13:30exact timeWWDC26 268@14:43exact time
  • demonstratedCrucially, before comparing, the demo filters both runs to the exact same os_signpost interval “to ensure an accurate comparison without noise.” WWDC26 268@14:08exact time
  • demonstratedPinning a thread into the timeline to inspect it alongside other tracks — via the inspector in WWDC26, and via secondary-click › Pin Thread in Timeline in WWDC25. WWDC26 268@22:27exact timeWWDC25 308@16:37exact time
  • demonstratedGetting back to source. From a call tree node, secondary-click › Reveal in Xcode opens the implementation; from the heaviest stack trace, Open in Source Viewer replaces the call tree with an inline source view. WWDC23 10248@19:34exact timeWWDC23 10248@24:01exact time
  • demonstratedLaunching straight from a unit test. In Xcode’s test navigator, secondary-click a test name and choose its Profile item to open Instruments on exactly that test. WWDC25 308@10:51exact time
  • statedSymbolication is what turns addresses into the function names a call tree needs; a whole session is devoted to how it works and to the sources of debug information you must configure. WWDC21 10211@0:22exact timeWWDC21 10211@0:34exact time
A concrete workflow12
  1. Reproduce the symptom by hand first, and know which gesture triggers it.
  2. In Xcode, Product › Profile (release build, correct device).
  3. Pick a template that matches the question — Time Profiler if you don’t know yet.
  4. In the recorder settings, choose the recording mode: immediate to confirm your interactions are landing; deferred or windowed / last-few-seconds to cut overhead once you know what you’re reproducing.
  5. Record, perform only the problem interaction, stop.
  6. Find the interval — ideally via your own signpost in the Points of Interest track.
  7. Set Inspection Range and Zoom (Option-secondary-click) on that interval.
  8. Pin the thread you care about into the timeline.
  9. Read the detail view: start on the flame graph or heaviest stack trace to orient, switch to the call tree with Hide System Libraries on to find your own code, switch to Top Functions when cost is diffuse rather than concentrated.
  10. Reveal in Xcode / Open in Source Viewer to get to the line.
  11. Fix, re-record with the same signpost interval, and use Run Comparisons against the baseline to prove the delta.
  12. Save the .trace document — it is the shareable artifact.
Tips and tricks6
  • Profile a release build, always; a debug build’s data can be misleading. WWDC26 268@26:12exact time
  • Add an os_signpost interval around the operation you’re tuning before you start, so that both the inspection range and any later run comparison are anchored to the identical window. WWDC26 268@26:14exact time
  • Option-secondary-click gets you Set Inspection Range and Zoom in one gesture instead of two. WWDC23 10248@12:46exact time
  • With System Trace specifically: because it records in windowed mode, “make sure that when you reproduce the problem” you stop promptly — you only get those last few seconds. WWDC16 411@11:31exact time
  • Save a configured template to the Custom tab so a recurring investigation is one click next week. WWDC16 411@10:59exact time
  • Run comparisons are saved into the trace document, so the document — not a screenshot — is what you hand a teammate. WWDC26 268@14:48exact time
Interpretation traps5
  • statedA debug-build profile is not your app’s performance. The single most common way to waste an afternoon is optimizing something that only costs what it costs because optimizations were off. WWDC26 268@3:39exact time
  • stated“Hide System Libraries” hides, it does not exonerate. It removes system frames from the view to make your code findable; it does not mean the system frames were free. The session frames it purely as a focusing filter. WWDC23 10248@13:48exact time
  • statedThe heaviest stack trace also hides frames by default — it “hides subsequent function calls that don’t originate from your source code.” So the bottom of the heaviest stack is not necessarily the bottom of the real stack. WWDC23 10248@13:38exact time
  • inferredAn unnarrowed trace flatters nothing. Every modern demo narrows to an interval before reading numbers, because a whole-recording call tree averages your problem away with idle time. The research crew inferred this generalization from the consistent demonstrated practice across WWDC22 10082, WWDC23 10248, WWDC25 308 and WWDC26 268 — the sessions demonstrate the narrowing, they do not state the rule.
  • inferredImmediate mode changes what you are measuring. Streaming data out in real time raises signpost cost by roughly an order of magnitude. If signposts start appearing in your own profiles, suspect the recording mode before suspecting your code. WWDC19 414@7:05exact time
macOS / iOS applicability

Instruments is a Mac app used against both. The foundations here — templates, targets, recording modes, call-tree shaping, inspection ranges, source navigation — are platform-neutral in every session the crew read. System Trace specifically “works great in all four of our platforms.” The one clearly device-gated foundation is Processor Trace. WWDC16 411@2:33exact time

Documentation clarifications for current tooling2
This tool’s evidence gaps2
  • No session demonstrates Separate by Thread, Separate by State or Invert Call Tree by name. The crew searched all 26 transcripts. These options exist in Instruments; there is no session citation for them here and none was manufactured.
  • No session demonstrates saving and re-sharing a .trace file as an explicit workflow step. WWDC19 411 only states that trace documents can be saved and reopened.

Time Profiler & CPU Profiler

CPU, time & responsiveness

What code was on the CPU — and, since WWDC25, which of the two samplers to use.

The question it answers

While my app was slow, what code was actually running on the CPU, and how much of the time did each part account for? It does not answer “why is my thread not running.”

What the sessions show and say10
  • statedThe sampling model, stated precisely. Time Profiler “uses a hardware timer to sample the state of your app’s execution at regular intervals. The default sampling rate is one millisecond. When the timer fires, it records the current call stack on every core.” WWDC18 independently confirms the 1 ms interval. WWDC26 268@7:29exact timeWWDC26 268@7:35exact timeWWDC18 407@14:56exact time
  • demonstratedHow samples become weights. A toy example: “In this first sample, main calls saveNote. In the call tree, each of these functions receives a weight of 1.” The longer a function runs, “the more likely it will be sampled.” WWDC26 268@7:48exact timeWWDC26 268@8:41exact time
  • demonstratedThe heaviest stack trace appears in the Extended Detail view and is “the set of functions that were” most sampled; clicking a frame in it reveals the corresponding node in the call tree. WWDC19 411@14:17exact timeWWDC23 10248@13:27exact time
  • demonstratedTime Profiler cannot distinguish one slow call from many fast ones. The worked Turtle/Unicorn example: with the same sample counts, “it could also be that this is a very fast Turtle, and Unicorn takes much longer, or other combinations. All of these scenarios would create the same data in Time Profiler.” The stated remedy: “To measure the execution time of a specific function, use os_signposts.” WWDC23 10248@16:37exact timeWWDC23 10248@16:45exact timeWWDC23 10248@16:50exact time
  • statedWhich direction to look. For a long-running function, look at its implementation and callees — “look further down”. For a function called very often, look at what calls it and whether you can call it less — “look further up.” WWDC23 10248@15:51exact timeWWDC23 10248@16:10exact time
  • statedTime Profiler only sees running threads. “time profiler only samples what’s actually running”, whereas the threads in question “are spending a lot of time in the blocked and runnable states.” WWDC26 restates it: “Because Time Profiler only monitors active CPU cycles, it provides no visibility into these events” — file I/O waits, lock waits, IPC waits. WWDC16 411@18:38exact timeWWDC26 268@2:54exact time
  • statedWhy aliasing matters on Apple silicon specifically: “Apple silicon CPUs are asymmetric and some of them will run at a slower, but more power-efficient clock frequency than others. Individual CPUs that scale up their frequency will be sampled more often, without Time Profiler’s bias against faster-running CPUs.” WWDC25 308@10:28exact timeWWDC25 308@10:36exact time
  • statedTime Profiler still has a stated strength: it “is helpful for analyzing how work is distributed over time or which threads are active at the same time.” WWDC25 308@9:31exact time
  • demonstratedHang labelling is built in. Since Xcode 14, “the Time Profiler also detects hangs and labels them directly in the corresponding process track”, available by default in “the Time Profiler and CPU Profiler instruments.” WWDC22 10082@6:04exact timeWWDC22 10082@8:02exact time
  • demonstratedReading a hang’s CPU signature. In the demo: “the main thread has barely any CPU usage during the hang interval, meaning the main thread was unresponsive because it was waiting on another thread, not because it was doing too much work itself.” WWDC22 10082@7:19exact time
A concrete workflow9
  1. Record with the Time Profiler (or the Swift Concurrency / Animation Hitches template, which contain it) on a release build.
  2. Narrow to the symptom’s interval.
  3. Pin and select the thread — usually the main thread first.
  4. Ask the CPU-usage question before the call-tree question: is this thread near 100% CPU, or near zero? High means a code bottleneck; low means it’s blocked, and Time Profiler will not tell you on what.
  5. If high: read the flame graph or heaviest stack trace to orient, then the call tree with Hide System Libraries on.
  6. Decide up or down: long-running function → inspect its callees; frequently-called function → inspect its callers and call it less.
  7. If cost is spread across many paths rather than concentrated, switch to Top Functions.
  8. For CPU-throughput optimization work (as opposed to responsiveness triage), re-record under CPU Profiler instead.
  9. To get a duration rather than a sample count for a specific function, add an os_signpost interval and re-record.
Tips and tricks4
  • Prefer CPU Profiler when the question is “which code burns the most CPU”; prefer Time Profiler when the question is how work is distributed over time or which threads are active at the same time. WWDC25 308@10:12exact time
  • Launch straight from a unit test (test navigator → secondary-click → Profile) so the recording contains your workload and almost nothing else. WWDC25 308@10:51exact time
  • Clicking a frame in the heaviest stack trace jumps the call tree to that node — faster than hunting down the tree by hand. WWDC23 10248@13:27exact time
  • When a stack is “already pretty deep in the call tree”, that depth itself is the signal that a single leaf isn’t the story. WWDC23 10248@13:31exact time
Interpretation traps5
  • demonstratedSample weight is not time. A weight of N samples means “this was on the CPU when the timer fired N times” — a statistical estimate, not a stopwatch. The Turtle/Unicorn example is the canonical demonstration that identical Time Profiler data can come from wildly different realities. WWDC23 10248@16:37exact time
  • statedAliasing can systematically lie to you, not just add noise. Periodic work synchronized to the 1 ms timer gets over-represented; this is exactly why Apple now points at CPU Profiler. WWDC25 308@9:57exact time
  • demonstrated“Low CPU but slow” is the signature of blocking, and Time Profiler is blind to it. A nearly empty main-thread call tree across a four-second hang does not mean nothing happened; it means the thread was off-CPU and you need System Trace or the Swift Concurrency instruments. WWDC22 10082@7:19exact timeWWDC26 268@2:54exact time
  • inferredA busy worker thread is not automatically the culprit — but in the WWDC22 demo it was the right next lead precisely because the main thread was idle and the Thread Performance Checker had already flagged a priority inversion. The inference chain matters; the busy thread alone would not have proven it. Inferred by the crew from the demonstrated sequence. WWDC22 10082@7:19exact time
  • inferredHide System Libraries can make an all-system-time problem look like an empty call tree. Crew inference — no session states this; it follows from what the filter does.
macOS / iOS applicability

Both. Nothing in any Time Profiler / CPU Profiler demo the crew read is platform-gated. The aliasing / asymmetric-core argument is specific to Apple silicon, which covers modern Macs and all iOS devices. WWDC25 308@10:28exact time

Documentation clarifications for current tooling2
This tool’s evidence gaps3
  • “Using Time Profiler in Instruments” (WWDC 2016, session 418) is no longer reachable and must not be cited. Independently re-confirmed in this task: the URL redirects to the WWDC16 index, the page carries no session title and zero transcript sentences.
  • No session explicitly contrasts CPU time vs wall-clock time as named concepts. The substance is covered (samples vs duration, on-CPU vs off-CPU); the terminology is not, and no citation was manufactured for it.
  • No demonstration of changing the sampling rate away from the 1 ms default.

System Trace

Thread State Trace · System Call Trace · Narrative view
CPU, time & responsiveness

The complement to Time Profiler: why a thread is not running.

The question it answers

My thread is not running — why not? What is it waiting on, who is holding it up, and what did the kernel do on my behalf?

What the sessions show and say14
  • statedWhat it records. Taking a System Trace recording “puts the kernel into a special tracing mode that records all the scheduling activity, system calls, and virtual memory operations that are occurring.” WWDC16 411@2:40exact time
  • statedWhy an app developer cares. A user “doesn’t see your application as a peer with all these system services and daemons contending for shared resources”; if your app “stutters due to a flurry of virtual memory activity or due to maybe a misprioritized thread, they’re going to come to you looking for a fix.” WWDC16 411@2:00exact time
  • statedThe three second-order effects it exists to surface: “increases in preemption, lock contention, and virtual memory activity.” WWDC16 411@1:05exact time
  • demonstratedThread states are visible in the track and hoverable. “if you hover over the thread states, you’ll see it was running for this period… Then it was blocked for a little while. Then it was runnable for kind of a long time, so that means it’s not actually running.” WWDC16 411@17:54exact timeWWDC16 411@18:06exact time
  • demonstratedThe colour legend, read out against a real trace: “gray means it’s blocked, meaning that the thread isn’t doing any work. Red means it’s runnable, meaning that there’s work scheduled to [do but it lacks] resources. Orange means it’s preempted, meaning that it was doing work [and got evicted].” WWDC19 423@23:21exact timeWWDC19 423@23:35exact time
  • demonstrated“Made runnable by” — who released the lock. “now it’ll show you which thread made your thread runnable. So basically who released that lock so that you could take it.” Independently demonstrated again in an app-launch trace: “afterwards, it was unblocked, or made runnable, by thread 0x12253.” WWDC16 411@17:01exact timeWWDC19 423@30:18exact time
  • demonstratedThe Narrative view is the detail view that “tells us the story of the thread”, showing the entry for a blocked state. WWDC23 10248@37:47exact time
  • demonstratedA concrete blocking diagnosis. “the thread was blocked for 6.64 seconds and it was blocked because it was calling mach_msg2_trap, a syscall.” The backtrace beside it is not an aggregation: “It is the precise backtrace of the mach_msg2_trap syscall that caused the thread to be blocked.” That stack then showed the syscall came from allocating an MLModel. 6.64 s is that demo’s number. WWDC23 10248@38:03exact timeWWDC23 10248@38:15exact timeWWDC23 10248@38:28exact time
  • statedThe modern framing of the same machinery. “when a resource isn’t immediately available a thread enters the blocked state. When this occurs, the kernel evicts the thread from the processor. Only when the resource is finally ready, the thread becomes runnable, and returns to the core.” And on the runnable state: “the thread doesn’t immediately start executing. It first enters the runnable state, which means the resource is ready” — i.e. runnable time is queueing time. WWDC26 268@20:59exact timeWWDC26 268@21:18exact time
  • demonstratedReading syscall intervals correctly. “The purple intervals you see indicate that a system call is running. However, just because a syscall is active does not mean the thread is actually executing the application code.” Selecting one segment highlights more than you clicked, because it “visualizes one continuous write system call that spans both on and off-core time.” WWDC26 268@22:52exact timeWWDC26 268@23:10exact time
  • demonstratedReading a System Trace overlay for a hang. “the red line indicates system calls, the purple graph indicates virtual memory faults, and the horizontal blue bar indicates the main thread is busy doing work.” Note the colour conventions here differ from WWDC26’s purple-for-syscall — see the traps. WWDC21 10258@12:13exact time
  • statedWhat System Trace adds over Time Profiler, stated compactly: it “adds more context with data on system calls, VM faults, I/O, as well as inter and intra-process interactions.” WWDC21 10258@11:40exact time
  • statedThe two sub-instruments by name. “The Thread State Trace instrument shows a timeline of the thread’s state”; “The System Call Trace shows a narrative that details the system calls entered and how long they took.” WWDC21 10181@10:06exact timeWWDC21 10181@10:22exact time
  • demonstratedWorkflow for isolating one thread’s story: “select the thread, create a time filter selection… and then change the detail section from the narrative” view. WWDC16 411@20:28exact time
A concrete workflow7
  1. Reach for System Trace after Time Profiler has shown you a low-CPU stall — not before.
  2. Record with the System Trace template (windowed by default) and stop promptly after reproducing.
  3. Narrow to the stall interval.
  4. Pin the stalled thread and read its thread-state band: how much is blocked (grey), runnable (red), preempted (orange)?
  5. For a blocked stretch, open the Narrative view to get the blocking reason, the duration, and the precise backtrace of the blocking syscall.
  6. Follow “made runnable by” to the thread that released you — that’s your contention partner.
  7. For runnable-heavy stretches, you have a scheduling or priority problem, not a code-speed problem.
Tips and tricks3
  • Long runnable time is the tell for a priority or contention problem: the work was ready and the thread still wasn’t on a core. WWDC16 411@18:06exact time
  • The backtrace next to a blocked state in the Narrative view is exact, not aggregated — you can trust it the way you cannot trust a sampled stack. WWDC23 10248@38:15exact time
  • Because the template records in windowed mode, reproduce first and stop immediately; you only keep the last few seconds. WWDC16 411@11:31exact time
Interpretation traps5
  • statedThe single most important trap in this whole cluster: “to determine whether a blocked thread is a responsiveness issue or not, look to the Hangs instrument, not the thread states instrument. So a blocked main thread does not imply an unresponsive main thread.” A main thread parked in the run loop waiting for events is blocked and perfectly healthy. WWDC23 10248@41:12exact time
  • demonstratedAn active syscall interval is not the thread executing your code. A long purple band can be almost entirely off-core waiting. WWDC26 268@22:52exact time
  • demonstratedA syscall interval can span on- and off-core time, so clicking one visual segment may select more than you expected — that is correct behaviour, not a UI glitch. WWDC26 268@23:10exact time
  • inferredColour conventions are not stable across sessions. WWDC21 10258 reads red as syscalls and purple as VM faults in a System Trace overlay; WWDC26 268 reads purple as a running syscall in the modern System Trace track. Read the legend in your own trace rather than trusting a colour memorized from a video. Crew inference from the divergence between those two cited statements. WWDC21 10258@12:13exact timeWWDC26 268@22:52exact time
  • statedData volume is the hidden constraint. The recording “can accumulate over time. It could be a lot of data” — which is why windowed mode is the default. WWDC16 411@2:44exact time
macOS / iOS applicability

Explicitly both, and more: “It works great in all four of our platforms.” WWDC16 411@2:33exact time

Documentation clarifications for current tooling2
  • WWDC16 411 is the only deep System Trace session in Apple’s Performance index and shows Instruments 8. Its concepts are intact; its UI is a decade old. The current UI for the same analysis is demonstrated in WWDC26 268 from 20:49 onward — cross-read them. WWDC26 268@20:49exact time
  • Improving app responsiveness. DocsImproving app responsivenessdoc
This tool’s evidence gaps2
  • No modern (2023+) session re-teaches virtual memory events in System Trace at depth; WWDC16 411 remains the reference.
  • No session demonstrates the System Call Trace instrument’s narrative in a full end-to-end fix; WWDC21 10181 only describes it.

Hangs and main-thread blocking

Hangs instrument · on-device hang detection
CPU, time & responsiveness

Discrete interactions that stop responding — 250 ms and up.

The question it answers

Why did my app stop responding to touches, and for how long?

What the sessions show and say13
  • statedThe definition, verbatim. “A hang is reported when the main thread is busy doing work or waiting on another thread or system resource, causing a delay in updating the view content by at least 250 milliseconds. The main thread is also unavailable to process new user interactions until the hang is resolved.” A second session gives the same threshold. WWDC22 10082@1:26exact timeWWDC22 10082@1:35exact timeWWDC21 10181@8:32exact timeWWDC21 10181@8:37exact time
  • statedMicro hangs. “most of our tools start reporting hangs by default starting at 250 ms, but we call these ‘micro hangs’ as they are easy to ignore. Depending on the context, those might be OK, but often they are not.” WWDC23 10248@5:21exact time
  • demonstratedWhy micro hangs still matter — demonstrated, not hand-waved: the same code “was slow on an iPad, simply because the screen was bigger. This is one of the reasons why you should also fix micro hangs. What you might see as a micro hang during testing at your desk might be a major hang for some of your users under different conditions.” WWDC23 10248@22:45exact time
  • demonstratedThread Performance Checker (Xcode 14+). It “notifies you in the Xcode Issue Navigator when it detects priority inversions and non-UI work on the main thread of your app, both of which are common causes of hangs” — and it works “while debugging your app without actively tracing it.” Enabled from the Diagnostics section of the scheme. WWDC22 10082@3:55exact timeWWDC22 10082@4:49exact timeWWDC22 10082@5:30exact time
  • demonstratedPriority inversion, in the demo’s terms: “a higher priority thread was attempting to be synchronized with a lower priority thread. This may indicate the hang we are noticing is caused by the main thread waiting on different lower-priority threads.” WWDC22 10082@5:13exact time
  • demonstratedThread Performance Checker now also surfaces in tests (Xcode 26): “Tests can now surface framework runtime issues as well as call out threading problems using the Thread Performance Checker.” WWDC25 247@35:37exact time
  • statedThe Hangs instrument. Hang detection and labelling is on by default in Time Profiler and CPU Profiler; separately “There is also a new standalone hang tracing instrument that you can add to any trace documents… it allows you to configure a hang duration threshold to find specific periods of unresponsiveness.” WWDC23’s document “contains the Time Profiler instrument and the Hangs instrument, both of which will be useful for our analysis.” WWDC22 10082@8:06exact timeWWDC23 10248@10:15exact time
  • statedSemaphores are called out as an anti-pattern: “be aware of semaphore use, as they do not propagate priority and can lengthen a hang due to preemption. A common anti-pattern is when trying to make asynchronous API [synchronous].” WWDC21 10258@9:14exact time
  • demonstratedOn-device hang detection (iOS 16+). Enabled at Settings › Developer › Hang Detection, available “for development-signed or TestFlight apps”, giving real-time notifications plus diagnostics without Xcode or tracing. The Hang Threshold is configurable, minimum 250 ms, and “can be bumped to 500 milliseconds or higher.” WWDC22 10082@9:11exact timeWWDC22 10082@9:52exact timeWWDC22 10082@9:58exact time
  • demonstratedWhat you get per hang: “both a text-based hang log and a tailspin”. The text log “has less information, but can give us an understanding of the hang at a glance”; for a deeper investigation, open the tailspin in Instruments. Diagnostics are “best-effort and processed in the background at a low priority to minimize performance overhead”, so they can take a while to appear. WWDC22 10082@10:30exact timeWWDC22 10082@10:58exact time
  • demonstratedWhy the desk misses real hangs. A synchronous network request on the main thread showed no delay “at my desk with Xcode and a strong network connection”, but hung for over three seconds in the field. “It is important to test the beta version of your app under these different, real-world conditions.” WWDC22 10082@11:33exact time
  • statedThe WWDC26 triage question — the cleanest formulation of the whole cluster. After Time Profiler orients you, “what is the CPU doing during the hang? If CPU usage is high, then the thread is busy and the work is taking too long… If the application hangs but the processor is idle, optimizing algorithms won’t be helpful. This, usually, suggest[s] that the main thread is stuck waiting for a resource to free up” — commonly file I/O, a synchronization lock, or IPC. WWDC26 268@2:02exact timeWWDC26 268@2:33exact timeWWDC26 268@2:46exact time
  • statedThe two fixes for the high-CPU branch: “The first is code optimization: refactoring algorithms to execute faster. But, if the heavy workload is unavoidable, resolve it by offloading the work to a background task so the user interface remains responsive.” WWDC26 268@2:18exact time
A concrete workflow7
  1. Before tracing anything, enable the Thread Performance Checker in the scheme’s Diagnostics section and just run the app. Priority inversions and main-thread non-UI work show up in the Issue Navigator for free.
  2. Reproduce under Product › Profile with a template containing the Hangs instrument.
  3. Find the labelled hang interval in the process track; the label carries the duration.
  4. Triple-click or Option-secondary-click to filter to exactly that interval.
  5. Look at main-thread CPU usage first. High CPU → code bottleneck: read the call tree / Top Functions, then either optimize or move the work off the main thread. Low CPU → blocking: go to System Trace (thread states, Narrative, blocking backtrace) or the Swift Concurrency instruments.
  6. Fix, re-record, and confirm the hang label is gone — then check for a remaining micro hang.
  7. For the field: enable Settings › Developer › Hang Detection on a TestFlight build and test on bad networks; open the tailspin in Instruments for anything the text log can’t explain.
Tips and tricks5
  • The Thread Performance Checker costs you nothing to leave on during development and catches the two classic hang causes without tracing. WWDC22 10082@3:55exact time
  • Raise the on-device Hang Threshold above 250 ms when the 250 ms notifications drown out the hangs you actually care about. WWDC22 10082@9:58exact time
  • Test on a larger-screened device; the WWDC23 demo’s micro hang on iPhone was a real hang on iPad purely because of screen size. WWDC23 10248@22:45exact time
  • Test on a weak network; a synchronous request that is invisible at your desk is a three-second hang in the field. WWDC22 10082@11:33exact time
  • Don’t wrap async APIs in semaphores to make them synchronous — priority does not propagate. WWDC21 10258@9:14exact time
Interpretation traps5
  • stated“Hang” does not mean “busy”. The definition explicitly includes waiting. An idle main thread for four seconds is a hang. This is the misconception that sends people hunting for a hot loop that doesn’t exist. WWDC22 10082@1:26exact time
  • statedBlocked ≠ unresponsive. Use the Hangs instrument to decide, never the thread-states track alone. WWDC23 10248@41:12exact time
  • inferredA hang label’s duration is the unresponsiveness, not the cost of any one function. Crew inference — follows from the stated definition; no session phrases it this way.
  • demonstratedFixing the reproduction is not fixing the hang. The desk → TestFlight → Organizer arc exists because each phase surfaces hangs the previous one structurally cannot. WWDC22 10082@11:33exact time
  • demonstratedOn-device diagnostics are best-effort and delayed, so “no log yet” is not “no hang”. WWDC22 10082@10:30exact time
macOS / iOS applicability

The concept and the Instruments tooling (Hangs instrument, Time Profiler hang labelling, Thread Performance Checker) are demonstrated on iOS but are not stated to be iOS-only. On-device hang detection is explicitly iOS 16, reached through Settings › Developer, so it is an iOS/iPadOS feature as demonstrated. Apple’s documentation notes macOS’s own vocabulary for the same phenomenon — “spin based on the spinning wait cursor that appears in macOS when an app is unresponsive” — and gives a perception threshold for discrete interactions: “A delay of less than 100 ms in a discrete user interaction is rarely noticeable, but even a few hundred milliseconds can make people feel that an app is unresponsive.” WWDC22 10082@9:11exact timeDocsUnderstanding hangs in your appdoc

Documentation clarifications for current tooling2
This tool’s evidence gaps2
  • The Xcode Organizer hang reports portion of WWDC22 10082 is genuinely taught in that session but was written up by the app-level crew instead — see the Organizer card.
  • No session demonstrates opening a tailspin in Instruments step by step; WWDC22 10082 only states you can.

Hitches and the render loop

Animation Hitches template · hitch time ratio
CPU, time & responsiveness

Continuous motion that stutters without ever freezing.

The question it answers

My scrolling or animation stutters even though the app never freezes — which frame missed its deadline, and in which phase?

What the sessions show and say13
  • statedHitch vs hang. A hitch is an interruption in continuous motion, not a period of unresponsiveness: “Hitches are caused when the Render Loop fails to finish a frame on time.” Apple’s documentation draws the same line: hitches “happen during continuous interactions, like scrolling or dragging, or during animations”, whereas a hang is the delay in a discrete interaction. Tech Talks 10855@1:33exact timeDocsUnderstanding hitches in your appdoc
  • demonstratedThe render loop has five phases, demonstrated in order: event → commit → render prepare → render execute → display. “The loop begins with the first phase, the event phase. Here your app handles touch events and decides if a change is needed in the UI. Next is the commit phase. In the commit phase, your app updates its UI and submits that to the render server for rendering. On the next VSYNC, the render server takes that submission, and in the render prepare phase, prepares it for drawing on the GPU. In the render execute phase, the GPU draws your UI into a final image, so on the next VSYNC the frame can be displayed to your user.” Tech Talks 10855@3:15exact timeTech Talks 10855@3:19exact time
  • statedThe render server does work on your behalf, and it’s still your problem: “Although it happens in a separate process, it does work on your app’s behalf, so it’s on you to ensure your layer tree can be processed and drawn in time.” Tech Talks 10855@3:57exact time
  • statedTwo types of hitch: “a commit hitch and a render hitch.” Tech Talks 10855@11:20exact time
  • demonstratedInside the commit phase, in sequence: layout runs parent-to-child (“Layout is a common performance bottleneck, so keep in mind that your app only has a couple of milliseconds to complete this work”), then setNeedsDisplay drawing into texture-backed Core Graphics contexts, then “the entire altered layer tree is collected and sent to the render server.” Tech Talks 10855@5:10exact timeTech Talks 10855@5:50exact time
  • demonstratedInside the render phases: in prepare “the render server iterates through the app’s layer tree and prepares a linear pipeline that the GPU can then execute”, working back-to-front; in execute “each layer is composited into a final texture”, and “Some layers can take longer to render, and this is another common performance bottleneck”. Tech Talks 10855@6:04exact timeTech Talks 10855@6:30exact time
  • statedHitch time ratio, defined. “Hitch time ratio is the total hitch time in an interval divided by its duration. Because it’s normalized to total time, we can compare it across experiences. It’s measured in hitch milliseconds per second.” WWDC20 restates it and gives the motivation: a raw hitch-time total “over a one-second test cannot be compared to that over a ten-second test.” Tech Talks 10855@9:30exact timeWWDC20 10077@3:24exact time
  • statedThe 2020 target thresholds, read out on screen: “anything under five hitch milliseconds per second is considered good and mostly unnoticeable by a user. Between five and ten hitch milliseconds per second, the user is going to notice some interruptions, and these should be investigated. Over ten hitch milliseconds per second, hitches are greatly impacting the user experience, and you should immediately investigate.” WWDC20 gives the same 5 / 10 bands. See the traps — Apple’s current Organizer documentation gives different numbers. Tech Talks 10855@10:51exact timeTech Talks 10855@11:05exact timeWWDC20 10077@3:44exact time
  • demonstratedThe Animation Hitches template. “you can use the new Animation Hitches template in Instruments to detect and investigate hitches”, and usefully “the animation hitches template includes Time Profiler”, so you can go straight from a hitching commit to the code that made it expensive. Tech Talks 10856@10:43exact timeTech Talks 10856@5:34exact time
  • demonstratedReading a commit hitch: select the main thread of the process, display its call tree, and “We can see that this call tree is originated from a commit transaction”. Tech Talks 10856@5:54exact time
  • demonstratedRender hitches are “hitches caused by slowness” in the render prepare and render execute phases; the session uses the animation hitches template plus the Xcode view debugger to optimize the layer tree. Tech Talks 10857@0:22exact timeTech Talks 10857@6:43exact time
  • statedHitches are a first-class instrument in the modern roster: “we have the Hangs and Hitches instruments, which keep track of your app’s responsiveness.” WWDC25 306@2:45exact time
  • statedAs a test metric: XCTHitchMetric “reports multiple metrics for your app’s hitch performance, such as Hitch Time Ratio.” WWDC25 247@35:06exact time
A concrete workflow6
  1. Record with the Animation Hitches template while performing the stuttering gesture.
  2. Find a hitching frame in the display track; note whether the hitch is attributed to the commit phase (your process) or the render phase (render server).
  3. Commit hitch → select the main thread, read the call tree rooted at the commit transaction; suspect layout, drawRect/setNeedsDisplay custom drawing, or work you scheduled into the event phase. The template’s bundled Time Profiler gets you to the code.
  4. Render hitch → look at the layer tree; use the Xcode view debugger to find layers that are expensive to prepare or composite.
  5. Quantify with hitch time ratio, not raw hitch milliseconds, so results compare across runs and devices.
  6. Lock in the win with XCTHitchMetric in a performance test, with a baseline.
Tips and tricks4
  • Always normalize to hitch time ratio; a raw total is meaningless across tests of different length. WWDC20 10077@3:24exact time
  • Your app has “only a couple of milliseconds” for layout in the commit phase — treat layout cost as a hard budget, not a soft one. Tech Talks 10855@5:10exact time
  • For hitch performance tests, use “a separate test scheme”, “select the release build configuration and disable the debugger”, and disable automatic screenshot collection. WWDC20 10077@8:33exact time
  • Set the measured average as a baseline so future runs are compared against it. WWDC20 10077@9:22exact time
Interpretation traps4
  • statedA hitch is not a hang, and the tooling is different. No amount of Hangs-instrument work will find a render-server compositing hitch. The deciding question is continuous motion vs discrete interaction. Tech Talks 10855@1:33exact time
  • stated“My main thread looks fine” does not clear you. A render hitch happens in another process entirely, on your layer tree. Tech Talks 10855@3:57exact time
  • conflictThe published hitch-ratio numbers are not all the same numbers. See the dedicated conflict panel below — the 2020 talks and Apple’s current Organizer documentation give different bands, and Apple does not reconcile them anywhere the crews read.
  • statedFrames are pipelined. “this entire process is actually happening in parallel every frame”, so the phase where a hitch surfaces is not necessarily the phase you last changed. Tech Talks 10855@6:53exact time
macOS / iOS applicability

The three hitch Tech Talks are iOS-framed — session 10856 opens with views “rendered onto the display by iOS” — and the render-loop / VSYNC / Core Animation model they teach is the UIKit one. No session the crew read teaches hitch analysis on macOS. Treat the macOS applicability of this specific tooling as unverified, not as established. Tech Talks 10856@0:24exact time

Documentation clarifications for current tooling2
  • Understanding hitches in your app — current thresholds and the Organizer Hitches metric. DocsUnderstanding hitches in your appdoc
  • The three hitch talks are Tech Talks the crew dates to 2020, on the strength of another Apple session calling them “the… tech talks from 2020”. The Tech Talk pages themselves carry no year. They predate SwiftUI’s current instrument suite. WWDC21 10181@12:50exact time
This tool’s evidence gaps3
  • The Tech Talk pages carry no year in their URL or title. The 2020 attribution is second-hand, from WWDC21 10181.
  • The crew did not verify whether the Animation Hitches template still exists under that exact name in current Instruments; no session from 2023 onward that they read names it.
  • No macOS hitch coverage found.

Signposts and Points of Interest

os_signpost · OSSignposter
CPU, time & responsiveness

Making your own operations visible in the trace, with real durations.

The question it answers

Instruments shows me system activity — how do I make my app’s own operations visible in the trace, with real durations rather than sample counts?

What the sessions show and say12
  • statedThe Points of Interest instrument is deliberately empty until you fill it: it “is essentially a blank canvas where you tell Instruments what you consider” interesting. WWDC19 shows it literally empty before instrumentation and populated after. WWDC16 411@3:37exact timeWWDC19 411@10:50exact timeWWDC19 411@26:12exact time
  • demonstratedHow a signpost lands in that track: the category is the routing. You create a log handle with your subsystem identifier and the system’s points-of-interest category, and then in “Instruments, you can see that we have those points of interest prominently displayed.” WWDC26 does the same with modern API and says why: “I set the subsystem to ‘Demo App’ so that I know it’s related to my App. And, I set the category to points of interest, so that instruments will automatically surface this data in the points of interest track.” WWDC18 405@29:46exact timeWWDC18 405@30:25exact timeWWDC26 268@6:04exact time
  • demonstratedSignpost IDs pair a begin with an end. “As long as you pass the same signpost ID at the begin call site and the end call site” the two are associated; you can derive the ID from an object so “you don’t have to carry or store the signpost ID around”, and for genuinely concurrent operations “we’re going to create new signpost IDs for each of them.” WWDC18 405@8:16exact timeWWDC18 405@8:42exact timeWWDC18 405@9:51exact time
  • demonstratedThe modern API is OSSignposter: “I added an os.signpost interval around lasso selection. I achieved this by using the OSSignposter type which has APIs to start and stop tracing an interval.” WWDC26 268@5:46exact timeWWDC26 268@5:52exact time
  • statedSignposts are the stated fix for Time Profiler’s blind spot: “To measure the execution time of a specific function, use os_signposts.” WWDC23 10248@16:50exact time
  • demonstratedA signpost interval is a navigation handle in the trace. Both recent sessions use it the same way: “we’ll find the region where searches are happening in the Points of Interest track. Secondary-clicking on the region will offer to set the inspection range, limiting the detail view below to only the data captured in the signpost interval.” WWDC25 308@12:04exact timeWWDC26 268@19:30exact time
  • demonstratedAnd it is the precondition for trustworthy run comparison: “we first filter both runs to the exact same os_signpost interval”; the session’s closing advice is “leverage os_signpost to make sure your intervals for run comparisons are reliable.” WWDC26 268@14:11exact timeWWDC26 268@26:14exact time
  • demonstratedMissing signposts are a felt cost. On hangs it couldn’t contextualize: “Unfortunately, these don’t have any logs in the points of interest track to help contextualize what was happening at the time.” WWDC26 268@16:19exact time
  • statedThe cost model, given explicitly — and hedged by Apple as order-of-magnitude, since “device type, hardware model, OS version, system load, thermals and more” all matter: release build, all signposts — under a microsecond; off-by-default dynamic categories — low nanoseconds when not recording; dynamic stacks category — low microseconds (it records a call stack); streaming (immediate) mode — tens of microseconds, “significantly more expensive”. WWDC19 414@6:33exact timeWWDC19 414@6:39exact timeWWDC19 414@7:05exact time
  • statedThe stated mitigation: “you can use Instruments’ deferred or last few seconds mode instead of immediate mode. This keeps OSSignpost out of that streaming mode and reduces the overhead.” WWDC19 414@7:27exact time
  • demonstratedCustom instruments build on signposts — a signpost becomes a schema, whose fields become modeled data: you “define what’s called an os signpost interval schema”. WWDC18 410@24:14exact timeWWDC19 421@16:11exact time
  • statedCustom-instrument authors are advised that “the last five-second recording mode is by far the most efficient”, and that an instrument can opt out of immediate mode. WWDC18 410@54:00exact timeWWDC18 410@54:51exact time
A concrete workflow6
  1. Create a signposter with your subsystem and the points of interest category — the category is what makes it show up in that track automatically.
  2. Wrap the operation you care about (OSSignposter begin/end, or os_signpost with a matching signpost ID at both call sites).
  3. For concurrent operations, mint a distinct signpost ID per operation, or derive it from the object, so intervals don’t cross-pair.
  4. Record. Use deferred / last-few-seconds mode if signpost overhead matters.
  5. Use the Points of Interest region as your navigation handle: secondary-click → Set Inspection Range.
  6. After a fix, re-record and compare the same signpost interval across runs.
Tips and tricks4
  • Add signposts before you need them. The WWDC26 session visibly loses time to a hang it can’t contextualize because no signpost covers it. WWDC26 268@16:19exact time
  • The “points of interest” category is the whole trick — it is what auto-surfaces your data without writing a custom instrument. WWDC26 268@6:04exact time
  • If signposts start showing up in your own profiles, switch off immediate mode before you start deleting signposts. WWDC19 414@7:27exact time
  • Derive signpost IDs from the object the work is about, so you don’t have to thread an ID through your call stack. WWDC18 405@8:42exact time
Interpretation traps4
  • statedAn empty Points of Interest track means you didn’t instrument, not that nothing happened. It is a blank canvas by design. WWDC16 411@3:37exact time
  • statedSignpost cost is not one number. The sub-microsecond figure is for a release build not in streaming mode. Measuring in immediate mode and concluding “signposts are expensive” is the classic error. WWDC19 414@7:05exact time
  • demonstratedReused signpost IDs silently produce wrong intervals for overlapping work. WWDC18 405@9:51exact time
  • inferredA signpost interval measures wall-clock duration of your interval, not CPU consumed. Crew inference — the sessions use it as a duration handle; none state this contrast explicitly.
macOS / iOS applicability

os_signpost / OSSignposter are os-framework APIs used identically across Apple platforms in the sessions the crew read; nothing platform-gated appears. The Points of Interest instrument is demonstrated on both a Mac app (WWDC16 411) and iOS apps (WWDC26 268).

Documentation clarifications for current tooling3
  • OSSignposter — the modern Swift type used in WWDC26. DocsOSSignposterdoc
  • Recording performance data. DocsRecording performance datadoc
  • WWDC18 405 predates OSSignposter and shows the older os_signpost function form — the concepts transfer, the API surface has moved.
This tool’s evidence gaps3
  • Custom instruments are covered only to the depth this guide’s brief allows. WWDC18 410 and WWDC19 421 are full custom-instrument authoring sessions (schemas, modelers, the analysis core); they are cited here for the signpost→schema link only. A reader wanting to author an instrument needs those sessions in full.
  • No session after 2019 re-teaches signpost cost. The WWDC19 414 numbers are the most recent verifiable ones and are seven years old. Treat the magnitudes as directional.
  • No session demonstrates signpost metadata / arguments being charted in a custom instrument end-to-end.

Processor Trace and CPU Counters

CPU, time & responsiveness

Past the limits of sampling: every instruction, and which microarchitectural resource stalled.

The question it answers

Sampling has taken me as far as it can. What is the CPU actually executing, instruction by instruction, and which microarchitectural bottleneck is my hot loop hitting?

What the sessions show and say10
  • statedProcessor Trace records everything, not samples. “Starting in Instruments 16.3, Processor Trace can collect a complete trace of all the instructions your app’s process executes in user space”, with “only a negligible 1% impact to your app’s performance”. WWDC25 308@14:09exact timeWWDC25 308@14:28exact time
  • statedHardware requirement, stated in-session: “Processor Trace requires specialized CPU features that are only available on Mac and iPad Pro with M4 or iPhone with A18.” Device setup is needed first: on a Mac “under Privacy & Security and Developer Tools”, and on iPhone/iPad “in the Developer section”. WWDC25 308@14:31exact timeWWDC25 308@14:41exact timeWWDC25 308@14:46exact time
  • documentedApple’s documentation states it more precisely — and adds the OS floor and an important escape hatch. Recording requires “iPhone 16 and iPhone 16 Pro, or later / iPad Pro with M4 or later / Mac with M4 or later”, on “iOS 18.4 or later, iPadOS 18.4 or later, or macOS 15.4 or later”. But: “You can use Instruments on any Mac to analyze a saved processor trace file, including those that don’t support recording processor traces.” DocsAnalyzing CPU usage with Processor Tracedoc
  • statedThe flame graph it produces is different in kind. “most flame graphs show data from sampling and their cost is only an estimate based on a sample count. Processor Trace’s timeline flame graph is different: it shows the calls made over time”. WWDC25 308@16:59exact timeWWDC25 308@17:06exact time
  • documentedIt sees compiler-generated code you didn’t write. The instrument “includes time that your app spends in all functions, including code that the compiler generates, such as automatic reference counting (ARC) memory management code in Swift, and synthesized constructors and destructors in C++.” DocsAnalyzing CPU usage with Processor Tracedoc
  • statedYou don’t need to batch work for it. “Unlike sampling with CPU Profiler, you don’t need to batch up your work: even a single instance of the code you want to optimize can be enough.” WWDC25 308@14:55exact time
  • statedBut keep it short. “For the best experience with Processor Trace, try to limit your tracing to only a few seconds”, and “Processor Trace has to deal with a lot of data, so capturing and analyzing it can take some time.” WWDC25 308@14:52exact timeWWDC25 308@15:31exact time
  • demonstratedCPU Counters got preset modes. “This year, we’ve added preset modes for these counters to make them much easier to use. Instruments uses them in a guided, iterative methodology”, and the template offers “a guided configuration with curated modes to measure with”. Instruments “collect[s] CPU counter data for the threads used in our test and convert[s] them into bottleneck percentages”, with a description of a selected bottleneck shown in the extended detail. WWDC25 308@22:35exact timeWWDC25 308@23:11exact timeWWDC25 308@23:53exact timeWWDC25 308@24:17exact time
  • statedCPU Counters samples, so it needs a repeatable workload: “The CPU Counters instrument relies on sampling the workload, so we need to return to the test harness we used with CPU Profiler to measure throughput again.” WWDC25 308@22:56exact time
  • statedXcode 26’s overview frames Processor Trace the same way: “Unlike traditional sampling profilers that can miss critical code paths between sampling intervals, this tool reveals every branch taken and function called”. WWDC25 247@21:27exact time
A concrete workflow6
  1. Get a repeatable workload — a unit test is ideal — and wrap it in an os_signpost interval.
  2. Profile it with CPU Profiler first to find where cycles go.
  3. If you need exact call costs or suspect sampling is missing something, switch to the Processor Trace template — on qualifying hardware, for a few seconds at most.
  4. Pin the thread, expand the per-thread flame graph, and select a specific call; set inspection range and zoom onto one iteration.
  5. For microarchitectural tuning, record with the CPU Counters template, choose a preset mode, orient via Points of Interest, and read the bottleneck percentages.
  6. Re-measure throughput the same way after each change.
Tips and tricks3
Interpretation traps5
  • statedProcessor Trace’s flame-graph widths are real time, not sample estimates — do not read them with sampling intuitions, and do not read a sampling flame graph with Processor Trace intuitions. WWDC25 308@16:59exact time
  • documentedOverhead is ~1% for the processor trace itself, not for the whole session. Apple’s docs add: “When you profile your app using Instruments, the system performs other tracing activities, so you may see total runtime overhead that’s greater than 1%.” DocsAnalyzing CPU usage with Processor Tracedoc
  • documentedTime attributed to ARC retain/release or synthesized C++ constructors is real code you did not write. Seeing it is the point, not a tooling artifact. DocsAnalyzing CPU usage with Processor Tracedoc
  • statedA long Processor Trace is a self-inflicted wound — capture and analysis both scale with the data. WWDC25 308@15:31exact time
  • inferredThis is a micro-optimization tool. Nothing here helps a hang caused by blocking; it only answers “my CPU work is too slow.” Crew inference from the session’s framing; not stated as an exclusion. WWDC25 308@2:14exact time
macOS / iOS applicability

Both, but hardware-gated: Mac with M4 or later, iPad Pro with M4 or later, iPhone 16 / 16 Pro or later, on macOS 15.4 / iOS 18.4 / iPadOS 18.4 or later. Analysis of an existing trace works on any Mac. WWDC25 308@14:31exact timeDocsAnalyzing CPU usage with Processor Tracedoc

Documentation clarifications for current tooling1
This tool’s evidence gaps3
  • The crew did not verify the names or semantics of the individual CPU Counters preset modes; the session demonstrates the guided flow and “bottleneck percentages” without a readable mode list. Anyone building on specific mode names must verify separately.
  • No documentation page for the CPU Counters instrument specifically was verified.
  • Instruments 16.3 is the stated floor for Processor Trace; the crew did not verify what the current Instruments version is.

Swift Concurrency instruments

Swift Tasks · Swift Actors · Swift executors
CPU, time & responsiveness

Actor congestion: why async work is still on the main thread.

The question it answers

My work is async. Why is it running on the main actor, why isn’t it running in parallel, and what is occupying the main thread?

What the sessions show and say10
  • demonstratedThe instruments exist and are taught. “The Swift Tasks and Swift Actors instruments provide a full suite of tools to help you visualize and optimize your concurrency code”, via “the new Swift Concurrency template in Instruments”. WWDC22 110350@4:58exact time
  • statedThe problems they target: “thread pool exhaustion and continuation misuse”; “Continuation misuse causes leaks or crashes.” WWDC22 110350@0:48exact timeWWDC22 110350@3:13exact time
  • statedWhy the main actor is special: “The main Actor is a special Actor which executes all of its work on the main thread… because the main thread is so important for UI, it needs to be available and can’t be occupied by a long-running unit of work.” WWDC22 110350@3:34exact timeWWDC22 110350@3:43exact time
  • demonstratedReading it in the trace: start from “the top-level statistics provided to you by the Swift Tasks instrument”, beginning with Running Tasks; then “pin the Main Thread to the timeline” — “The Main Thread is being blocked by several long running Tasks.” WWDC22 110350@5:12exact timeWWDC22 110350@9:14exact time
  • demonstratedThe Swift Concurrency Tasks instrument as a hang tool. “For issues like this, where Swift Concurrency tasks don’t execute the way you expect them to, we have another useful instrument: the Swift Concurrency Tasks instrument… The Swift Tasks instrument adds a summary track to the document”, but “what’s more interesting for our case is the data it contributes to each thread track” — the useful data is the new graph inside the main thread track. WWDC23 10248@30:14exact timeWWDC23 10248@30:28exact timeWWDC23 10248@30:40exact time
  • statedThe root cause it exposes, stated as a rule: “Swift Concurrency Tasks, by default, inherit the actor isolation of the surrounding context.” WWDC23 10248@32:28exact time
  • demonstratedWWDC26 shows the modern version of the same bug and fix. Diagnosis: Time Profiler showed main thread CPU “around a 100%… these tasks are just taking too long to run on the Main Actor”; cause: “because this code was called from SwiftUI, it inherited the Main Actor context. These tasks will compete with critical UI updates for the Main Actor”. Fix: “adding the @concurrent attribute to the task initializer… will move the thumbnail rendering task off the Main Actor, and onto the global executor”, and “The swift compiler will check that this code change does[n’]t introduce any race conditions.” WWDC26 268@17:32exact timeWWDC26 268@18:00exact timeWWDC26 268@18:29exact time
  • demonstratedThe verification is visual and specific: “the Swift executors instrument shows that the thumbnail rendering tasks have moved from the Main Actor track to the global executor track”, and the fix “also allowed us to render these thumbnails in parallel.” WWDC26 268@18:47exact timeWWDC26 268@19:02exact time
  • statedWWDC26’s one-line rule for this tool: “When the tasks fight for resources, use the Swift Concurrency instrument to identify actor congestion.” WWDC26 268@25:51exact time
  • demonstratedChoosing the template up front: “Because my codebase utilizes Swift Concurrency, I will choose the Swift Concurrency template from the picker. Within this template, we still have access to the Time Profiler instrument.” WWDC26 268@3:43exact time
A concrete workflow7
  1. If the codebase uses Swift concurrency, start from the Swift Concurrency template — it still contains Time Profiler, so you lose nothing.
  2. Narrow to the stutter, pin the main thread.
  3. Read main-thread CPU: ~100% with async work on it is actor congestion, not blocking.
  4. Look at the Swift Tasks graph inside the main thread track (not just the summary track) to see which tasks are running there.
  5. Ask why they’re there — usually inherited actor isolation from a main-actor-isolated caller.
  6. Move them off (@concurrent, or otherwise breaking the isolation inheritance) and let the compiler check for races.
  7. Verify in the Swift executors track that the tasks moved from the Main Actor track to the global executor track — and check whether they now run in parallel.
Tips and tricks3
  • The interesting data is in the thread tracks, not the summary track — WWDC23 says this twice, once for Swift Concurrency and once for Thread States. WWDC23 10248@37:24exact time
  • 100% main-thread CPU during a hang in an async codebase points at actor congestion, and the Time Profiler call tree alone won’t say so. WWDC26 268@17:32exact time
  • The executor track is a before/after artifact: it visibly shows work relocating, which makes it an unusually clean way to prove a concurrency fix. WWDC26 268@18:47exact time
Interpretation traps3
  • stated“It’s async, so it’s off the main thread” is false. Inherited actor isolation is the default, and this is the single most-demonstrated concurrency performance bug across both sessions. WWDC23 10248@32:28exact time
  • inferredActor congestion looks like a CPU bottleneck in Time Profiler — high main-thread CPU — but the fix is relocation, not optimization. Optimizing the algorithm would have “worked” and still left the UI competing for the main actor. Crew inference from the WWDC26 demo’s reasoning. WWDC26 268@17:32exact timeWWDC26 268@18:29exact time
  • demonstratedA quiet Swift Tasks summary track is not evidence of health. WWDC23 10248@30:32exact time
macOS / iOS applicability

Swift concurrency and these instruments are platform-neutral; the demos are iOS/iPadOS apps. Nothing platform-gated is stated — the crew inferred applicability from the absence of any stated restriction, which is not an explicit Apple claim.

Documentation clarifications for current tooling2
  • WWDC22 110350 predates @concurrent and current isolation rules; WWDC26 268 is the current reference for this tool and for the fix idiom. Read 2022 for the instrument’s anatomy, 2026 for what to actually do.
  • Improving app responsiveness. DocsImproving app responsivenessdoc
This tool’s evidence gaps2
  • “Swift executors instrument” is the WWDC26 name; “Swift Tasks” and “Swift Actors” are the WWDC22 names, and “Swift Concurrency Tasks” the WWDC23 name. The crew could not verify from Apple pages whether these are renames of the same instruments or distinct ones. No mapping is asserted here.
  • “Embracing Swift Concurrency” (WWDC25) and “Embrace Swift generics” (WWDC22), both referenced by WWDC26 268, were not fetched — they are language sessions rather than tooling sessions.

04Memory, energy, network & disk

What you are charged for, what drains the battery, what goes on the wire, and what hits the NAND.

Schematic What counts toward footprint. Dirty and compressed are charged; clean is not. Compressed pages are still charged at their uncompressed size. Not a screenshot. WWDC21 10180@2:59exact timeWWDC18 416@1:34exact timeWWDC22 10106@5:00exact time
dirty — written by your app compressed — charged at uncompressed size clean — pageable, never written; not charged footprint = dirty + compressed 16 KB pages, indivisible: touch any byte and the whole page is dirty iOS has no disk swap — a compressor instead. That is the sharpest macOS divergence.
Schematic The five transaction states, in order. A missing state is evidence: no “waiting for response” means no server was involved. Not a screenshot. WWDC21 10212@7:08exact time
cache lookup blocked sending request waiting for response receiving response one transaction; a task is one or more of these often milliseconds — zoom before concluding it did not happen absent ⇒ answered from Local Cache, no server involved

Allocations

Memory, energy, network & disk

Where heap memory comes from, and whether the growth is a spike or a stair-step.

The question it answers

Where is my heap memory coming from, and is the growth temporary (a spike) or permanent (a stair-step)? Which call stack allocated it?

What the sessions show and say9
  • demonstratedThe Allocations template contains two instruments, Allocations and VM Tracker: Allocations records heap and VM events live, VM Tracker periodically snapshots all virtual memory. WWDC24 10173@6:14exact time
  • demonstratedReading a spike. Selecting a spike interval from its low point to its peak and sorting the statistics view by Persistent isolates what survived to the top of the spike; in the demo, @autoreleasepool content stood out with “hundreds” of entries. WWDC24 10173@8:18exact time
  • demonstratedAttributing churn to code. Switching the Lifespan filter to Created & Destroyed across a wide range, then switching the detail view to a call tree, attributes transient churn to code. The demo’s trace showed 8 GB of temporary allocations traced to a makeThumbnail() path. 8 GB is that demo’s number, not a measurement of any app. WWDC24 10173@9:06exact time
  • demonstratedMark Generation breaks persistent growth down by timespan: the first press groups all allocations created before that point that persisted to the end of the trace; each later press groups allocations made after the previous generation and before the new mark. WWDC24 10173@14:01exact timeWWDC24 1017313:57named section only
  • demonstratedExpanding a generation and ordering by growth size names the responsible types; individual allocation addresses are readable in the extended detail view and can be pasted into the Memory Graph Debugger’s filter bar. WWDC24 10173@15:16exact time
  • demonstratedAn imported memory graph populates the Allocations, Leaks and VM Tracker instruments in Instruments. WWDC24 10173@14:26exact time
  • demonstratedAllocations marks short-lived allocations as transient; a near-million-allocation count plus “nearly all transient” is presented as the two indicators of unnecessary allocation. WWDC25 312@8:53exact timeWWDC25 3128:17named section only
  • demonstratedThe detail-view filter field accepts a symbol substring (the demo typed QOI.init) to drop every call tree that does not contain it. WWDC25 312@13:53exact time
  • documentedGenerations are also the documented way to scope allocations to one feature: mark, exercise the feature, mark again. DocsGathering information about memory use § Profile your app using the Allocations instrumentdoc
A concrete workflow7
  1. Turn on MallocStackLogging in the scheme’s Diagnostics tab, so every allocation carries a backtrace.
  2. Product › Profile (release build, Instruments opens with the app as target), choose the Allocations template, Record.
  3. Exercise the suspect feature several times so the pattern repeats, then Stop.
  4. Spike shape → transient growth. Drag-select one spike low-to-high; sort statistics by Total Bytes, then by Persistent, to see both the bulk and the survivors.
  5. Widen the selection across all spikes, set Lifespan = Created & Destroyed, switch the detail jump-bar to Call Tree, and read the Heaviest Stack Trace; your frames are emphasised. Double-click a frame to open its source.
  6. Stair-step shape → persistent growth. Press Mark Generation at the quiet points between growth intervals; expand the generation that grew, order by growth size, and read the type.
  7. Copy an offending allocation’s address from the extended detail view and continue in the Memory Graph Debugger to answer why is this still alive.
Tips and tricks5
  • For heap analysis specifically, the Simulator is acceptable — “for heap analysis, though, the Simulator environment is a lot closer in behavior, and it’s fine to use for memory profiling” — unlike timing work, which needs a release build on a real device. WWDC24 10173@12:24exact time
  • Autorelease-pool content pages are themselves visible in Allocations, which makes them a useful tell for autorelease-driven spikes. WWDC24 10173@11:32exact time
  • The fix for that pattern is a nested autoreleasepool scope inside the loop body, draining per iteration. WWDC24 10173@11:58exact time
  • If the code is now too fast for Instruments to sample usefully, loop it (the demo looped parsing 50 times) to get a richer profile. WWDC25 312@16:42exact time
  • A trace file is portable: save it and hand it to a colleague. WWDC24 10173@7:32exact time
Interpretation traps4
  • demonstrated“Total bytes” is not footprint. In the WWDC24 demo the top spike category was IOSurface virtual memory, not heap — even in a session explicitly about the heap. Allocation totals answer “what churned”, not “what is the app charged for”. WWDC24 10173@8:18exact time
  • statedA spike is not harmless just because it comes back down. Three distinct costs are given: memory pressure and the system’s reaction (swap/compress/discard, background-task termination), possible termination of your own app, and long-term fragmentation. WWDC24 10173@7:55exact time
  • inferredGenerations are cumulative-with-a-cutoff, not “allocations in this window”. Generation A is everything created before the first mark that persisted to the end of the trace — a first mark placed late will swallow startup allocations. Crew’s inferred consequence: place the first mark after the app has settled but before the feature under test. WWDC24 10173@14:01exact time
  • statedRecording distorts what you are measuring. MallocStackLogging and Allocations track live data and cost memory and CPU; Leaks, VM Tracker and memory graphs are snapshot-based and suspend the target, which can visibly stutter or hang the app. The demo’s own trace ran “a little slower than last time, but that’s expected”. WWDC24 10173@32:11exact timeWWDC24 1017332:06named section onlyWWDC24 10173@6:40exact time
macOS / iOS applicability

The instrument and the whole workflow are cross-platform; the WWDC24 demo profiles an iOS app on device and on Simulator, and the WWDC25 Swift session profiles a command-line-style parser. Nothing in the Allocations workflow is iOS-only. The consequences of growth differ — see Memory footprint, where iOS/iPadOS enforce a footprint limit.

Documentation clarifications for current tooling2
This tool’s evidence gaps1
  • No session the crew read demonstrates the Allocations snapshot / mark-heap comparison UI under its current Xcode 26 naming; the generation workflow is shown, the newer statistics-view chrome is not narrated anywhere verifiable.

Leaks

Memory, energy, network & disk

Unreachable memory — and the three reasons a clean leak report proves little.

The question it answers

Which allocations are unreachable — lost forever, and unfreeable — as opposed to merely retained too long?

What the sessions show and say10
  • statedThe Leaks instrument takes periodic snapshots of the app’s memory to detect leaks. WWDC24 10173@5:06exact time
  • statedThe three-way model that makes leaks tractable: useful memory (reachable, will be used), abandoned memory (reachable, never used again — caches, singletons; counts against footprint and is pure waste), and leaked memory (unreachable). WWDC24 10173@20:26exact timeWWDC24 1017320:15named section only
  • demonstratedIn the Memory Graph Debugger, leaked allocations carry a yellow triangle; the Show only leaked allocations filter-bar button plus the project-types filter narrows a noisy graph to your code. WWDC24 10173@21:14exact time
  • demonstratedFixing one cycle cascaded: after breaking a three-node cycle, “the other leaks were resolved too” because those types were referenced by the leaked ones. WWDC24 10173@24:03exact time
  • demonstratedleaks on a memgraph prints a ROOT CYCLE marker for retain cycles and, with malloc stack logging on, an allocation call stack per leak. WWDC21 10180@12:07exact time
  • statedleaks was updated (as of WWDC18) to show not just leaked objects but the retain cycles they belong to, plus a backtrace for the root node when malloc stack logging was enabled. WWDC18 416@13:54exact time
  • statedA leak defined: “the process allocates an object and loses all references to it without ever deallocating it”; in Swift the common route is a retain cycle between two objects with no external reference. WWDC21 10180@10:31exact time
  • statedSwift closure contexts are the modern variant: when a closure captures values it allocates a heap block the Memory Graph Debugger labels closure context, and each closure context corresponds 1:1 with a live closure. Captures are strong by default. Closure metadata does not carry variable names, so every reference out of a closure context is simply labelled capture. WWDC24 10173@22:00exact timeWWDC24 1017321:54named section only
  • demonstratedA particularly easy mistake: assigning a method as a closure (generator = defaultAction) implicitly captures self and creates a cycle; wrapping it in an explicit closure lets you apply a capture list. WWDC24 10173@29:14exact time
  • demonstratedThe WWDC21 demo’s cycle is the textbook bidirectional-ownership bug — MealPlan.addItem stores the item and MenuItem.addPlan stores the plan, both strong — fixed by making one side weak. WWDC21 10180@13:48exact time

What the instrument structurally cannot do

  • statedReal leaks can be missed. Where the tools lack type information (C, and any untyped block) they scan conservatively, byte by byte, treating anything that looks like a pointer as a possible reference. A value that is really a number, flags, or random bytes can therefore keep a genuinely-leaked block “reachable”. WWDC24 10173@24:26exact timeWWDC24 1017324:13named section only
  • statedThe leak count legitimately moves up and down. Conservative references are non-deterministic because the heap is noisy, so a program that leaks five objects may report five, then four. WWDC24 10173@25:15exact time
  • statedNon-returning functions can appear to leak. For C noreturn functions or Swift functions returning Never, the compiler may optimise away cleanup, so local state (e.g. a Server parked on a thread forever) is reported as leaked. The suggested fix is to store the object into a global so the reference lands somewhere the tools can see. WWDC24 10173@25:37exact time
  • statedMitigation shown for authoring a test leak: put it in a loop that leaks 100 times, because in real apps leaking code runs repeatedly and the tools still catch the bug even if not every instance. WWDC24 10173@24:51exact time
A concrete workflow6
  1. Reproduce the suspect flow, then pause in the Memory Graph Debugger from Xcode’s debug bar.
  2. Click the leaked-allocations filter (triangle icon), then the project-types filter.
  3. Select a leaked node; walk the reference edges in the Inspector and note the ownership qualifier Instruments reports on each edge (strong / weak / unowned / unmanaged / conservative).
  4. For a closure context, use its allocation stack trace to jump to the code that created the closure, then add a capture list.
  5. Re-run and re-pause to confirm the type disappears — and turn the type filter off, because downstream leaks often clear at the same time.
  6. For batch / CI or Mac use, run leaks <memgraph> and look for ROOT CYCLE.
Tips and tricks2
  • A cascade is normal: fix the cycle at the root and the leaks that hung off it clear with it. Turn the type filter off before concluding a fix was partial. WWDC24 10173@24:03exact time
  • When authoring a deliberate test leak, loop it — conservative scanning may not catch a single instance. WWDC24 10173@24:51exact time
Interpretation traps4
  • demonstrated“Zero leaks” is a weak signal, for two independent reasons. Conservative scanning can hide real leaks, and leaks are not the expensive failure mode. In the WWDC18 demo the memgraph had no leaks at all and the app was still using over a gigabyte — the problem was image size. WWDC18 416@28:08exact time
  • statedAbandoned memory is invisible to leak checking by construction, because it is reachable — over-aggressive caches, data parked on singletons. It still counts against footprint. WWDC24 10173@20:26exact time
  • statedLeaking system types is possible but unusual: “leaks are usually caused directly by issues in the project.” WWDC24 10173@21:14exact time
  • statedSwift reference reporting depends on a build setting. If weak/unowned references are not showing up in your memory graph, check the project’s Reflection Metadata Level; the recommendation is the default All. WWDC24 10173@28:50exact time
macOS / iOS applicability

Both. The leaks command-line tool “can analyze macOS and Simulator processes directly, or investigate issues using already-captured memory graphs”. WWDC22 recommends leaks directly for a Mac game as the alternative to capturing a graph in Xcode. Crew inference: on a live iOS device process you go via a captured memgraph rather than attaching leaks to the process, since the sessions only ever demonstrate direct attach for macOS and Simulator. WWDC24 10173@4:43exact timeWWDC22 10106@29:45exact time

Documentation clarifications for current tooling1
  • Leaks remains a standard Instruments template in current Xcode — confirmed by the memory crew’s own local xctrace check, not by an Apple page. See the local tool check panel in Coverage boundaries.
This tool’s evidence gaps1
  • No session the crew read states the Leaks instrument’s snapshot interval, nor how to change it. “Periodic snapshots” is all the evidence there is.

Memory Graph Debugger and the command-line memory tools

vmmap · heap · leaks · malloc_history · footprint
Memory, energy, network & disk

Why is this allocation still alive, and where was it born?

The question it answers

Why does this specific allocation still exist — what chain of references is keeping it alive, and where was it allocated?

What the sessions show and say13
  • statedA memory graph is “a snapshot of all allocations and the references between them”; with MallocStackLogging it also carries a backtrace per allocation, and it is reachable straight from Xcode’s debug bar. More precisely: “Memgraphs record the address and size of each virtual memory region and each allocated malloc block, as well as pointers between those regions and blocks”. WWDC24 10173@4:16exact timeWWDC21 10180@7:17exact time
  • statedThe reference-type taxonomy that governs how much you can trust an edge: strong (definitely pointers, ARC-managed, explicit ownership), weak/unowned (definitely pointers, explicit non-ownership), unmanaged (pointers the runtime knows about but does not manage — “might be manual owning references, but then again, maybe not”), and uncertain/conservative (raw memory that merely looks like a pointer). WWDC24 10173@16:26exact timeWWDC24 1017316:00named section only
  • statedType-information quality is language-dependent: Swift and Objective-C are well described; C and C++ carry no reference-ownership information, so you only see conservative references, and the best the tools can do is name C++ types that have virtual methods. Types without virtual methods fall back to stack-trace-derived labels such as malloc in PalmTree::growCoconut(). WWDC24 10173@17:14exact time
  • demonstratedEnd-to-end demo: paste an address from Allocations into the Memory Graph Debugger filter bar, walk __DataStorage → PhotoThumbnail → dictionary → the static ThumbnailLoader.globalImageCache, then use the Inspector’s allocation backtrace to jump to the caching code and find a cache key built from Date() instead of the file’s creation date. WWDC24 10173@18:04exact timeWWDC24 10173@18:51exact time
  • demonstratedEnabling MallocStackLogging: a checkbox in the scheme’s Diagnostics tab. WWDC18 adds the practical refinement to prefer the live allocations option when the log is destined for a memgraph. WWDC24 10173@3:28exact timeWWDC18 416@15:42exact time
  • demonstratedExport for CLI use: File › Export Memgraph, then pass the memgraph to a command-line tool in place of the target process. WWDC18 416@10:53exact time
  • statedvmmap --summary prints per-region size, dirty and swapped; the swapped column is the pre-compression size of the data, not what it compressed down to. WWDC21 restates the mapping explicitly: “In this tool, the term swapped means compressed.” WWDC18 416@11:36exact timeWWDC21 10180@15:46exact time
  • demonstratedheap summarises heap objects by class with count, average and total size; it sorts by count by default, so --sortBySize is what you want when hunting a few enormous objects; heap --addresses <class> yields addresses to feed the next tool. WWDC18 416@14:59exact time
  • demonstratedheap -diffFrom <pre> <post> shows objects present in the later graph but not the earlier — the regression-triage entry point. WWDC21 10180@16:35exact time
  • demonstratedleaks --traceTree <address> gives the tree of objects referencing that address (useful without malloc stack logging); leaks --referenceTree gives a top-down tree of all memory with a best guess at roots, and --groupByType shrinks it. WWDC21 10180@17:50exact timeWWDC21 10180@18:44exact time
  • demonstratedmalloc_history --fullStacks <memgraph> <address> returns the allocation call stack — the “smoking gun” step. WWDC21 10180@19:30exact timeWWDC18 416@38:31exact time
  • statedfootprint recreates a high-level per-category breakdown from a memory graph; in that tool’s output the dirty size also includes swap and compressed, so read it as “total charged per category”. This is a different column convention from vmmap, which keeps dirty and swapped separate. WWDC22 10106@18:36exact timeWWDC22 10106@20:21exact timeWWDC22 10106@21:12exact time
  • demonstratedComposability is an explicit selling point: vmmap --pages <memgraph> | grep dylib | awk … to total dirty pages attributable to linked libraries. WWDC18 416@12:43exact time

The two decision trees the sessions actually give

  • statedWWDC18’s three-question chooser. (1) Want to see where an object was created? → malloc_history (requires MallocStackLogging). (2) Want to see what references an object or address? → leaks and its options. (3) Want to see how large a region or instance is? → vmmap and heap. Recommended jumping-off point: vmmap --summary against a memgraph, then follow the thread. WWDC18 416@16:48exact timeWWDC18 416@17:30exact time
  • statedWWDC21’s ordered triage recipe. (1) leaks first — check for leaks, use the MSL backtraces to fix them. (2) If the regression is not leaks: vmmap --summary to confirm the memory is in the heap (look at the MALLOC_* regions, and only at the dirty and swapped columns). (3) heap -diffFrom to see which object types grew. (4) If the culprits look obvious, heap --addresses to get addresses; if not, leaks --referenceTree for clues about where memory aggregates. (5) Finish on a specific address with leaks --traceTree and/or malloc_history. WWDC21 10180@27:48exact time
A concrete workflow6
  1. Capture: pause in the Memory Graph Debugger from Xcode’s debug bar, or File › Export Memory Graph for CLI work.
  2. Start broad with vmmap --summary on the memgraph to see where the memory actually is (heap vs some other VM region).
  3. If it is heap: heap -diffFrom between two graphs, or heap --sortBySize on one.
  4. Get an address, then answer who holds it with leaks --traceTree and where it came from with malloc_history --fullStacks.
  5. In the GUI, paste the address into the Memory Graph Debugger filter bar and walk the reference edges, reading the ownership qualifier on each.
  6. Use the Inspector’s allocation backtrace to jump to the line that created it.
Tips and tricks5
  • Read the man pages. Explicitly recommended for the advanced functionality. WWDC24 10173@4:43exact time
  • vmmap --verbose matters: by default vmmap collapses contiguous regions into one line, hiding per-region differences in dirty and compressed size. WWDC18 416@33:24exact time
  • A heuristic from the same demo: later-created VM regions tend to correspond to later app lifecycle events, so when a memgraph was captured during a spike, start at the last region rather than the biggest. Presented as “not necessarily, but as a general rule”. WWDC18 416@34:07exact time
  • Terminal output beats the graphical view for sharing: it can be searched, filtered, and pasted into a bug report. WWDC18 416@36:19exact time
  • heap on an exported graph is a quick way to price a design choice: in the WWDC24 demo, one million ByteProducers with weak references produced one Swift weak reference storage allocation each, “almost as much memory as the ByteProducers themselves”. WWDC24 10173@30:02exact time
Interpretation traps5
  • statedA conservative edge is a guess, not a fact. Treat an “uncertain” reference from untyped memory as a lead, not proof of ownership. WWDC24 10173@16:50exact time
  • statedvmmap’s virtual-size column is close to meaningless for footprint questions — “virtual means not real… memory that the app has requested but isn’t necessarily using”. Read dirty + swapped. WWDC18 416@30:20exact time
  • statedfootprint and vmmap disagree about what “dirty” means on purpose. footprint folds swap/compressed into dirty; vmmap does not. Comparing the two columns across tools without noticing this will mis-add. WWDC22 10106@20:21exact timeWWDC22 10106@21:12exact time
  • demonstratedMalloc-stack-logging changes which zone your allocations land in. With MSL enabled, heap allocations go to the MallocStackLoggingLiteZone, not the DefaultMallocZone — so when reading vmmap --summary’s per-zone fragmentation table you must look at the MSL zone, which is exactly the one a reader would assume is diagnostic noise. WWDC21 10180@25:00exact time
  • statedheap’s “non-object” type is not a dead end but it is a detour. In Swift it usually means raw malloc’d bytes and needs the address-then-malloc_history path to identify. WWDC21 10180@17:01exact time
macOS / iOS applicability

The CLI tools operate on exported memgraphs on your Mac for any platform, and can additionally attach directly to macOS and Simulator processes. The Memory Graph Debugger itself is an Xcode feature that works against a device or Simulator session. WWDC24 10173@4:43exact time

Documentation clarifications for current tooling1
  • Current naming: the checkbox is “Malloc Stack” in the Diagnostics area of the scheme’s Run settings; export via File › Export Memory Graph; and the doc still points readers at WWDC18 session 416 for the command-line tools. Note the naming drift: WWDC18 says “Export Memgraph”, current docs say “Export Memory Graph”. DocsGathering information about memory use § Inspect the debug memory graphdoc
This tool’s evidence gaps1
  • malloc_history’s behaviour without --fullStacks, and vmmap’s full flag surface, are not covered in any session the crew read — the sessions defer to the man pages, and so does this guide.

Memory footprint and the memory model

Memory, energy, network & disk

What the system actually charges you for, and what gets you terminated.

The question it answers

What is my app actually charged for, and what gets it terminated?

What the sessions show and say7
  • statedThe model. Memory splits into dirty (written by the app: heap/malloc, decoded image buffers, frameworks’ data-dirty sections), compressed (dirty pages not recently accessed, squeezed by the memory compressor, decompressed on access), and clean (never written / pageable — memory-mapped read-only files, frameworks’ DATA_CONST). WWDC21 10180@2:11exact time
  • statedFootprint = dirty + compressed. Clean memory does not count. Stated independently by two sessions. WWDC21 10180@2:59exact timeWWDC18 416@7:40exact time
  • statediOS has no traditional disk swap; it has a memory compressor, introduced in iOS 7. WWDC21 puts it as “we do not have the concept of swap on iOS, which is specific to macOS”. This is the single most important macOS/iOS divergence in the memory material. WWDC18 416@5:00exact timeWWDC21 10180@2:35exact time
  • statedPages are 16 KB and indivisible: writing any part of a page dirties the whole page and you are charged for all of it. WWDC24 adds that only dirty and swapped count toward footprint and that “in most applications, the heap will be responsible for the majority of that footprint”. WWDC18 416@1:34exact timeWWDC21 10180@21:27exact timeWWDC24 10173@2:35exact time
  • statedCompressed pages are still charged at their uncompressed size. WWDC22 10106@5:00exact time
  • statedOn Apple silicon, accessed Metal resources count as dirty app memory, because CPU and GPU share unified memory. WWDC22 10106@4:30exact time
  • statedLinked frameworks cost you dirty memory, unavoidably. If you own a framework, singletons and global initialisers are the lever, since a singleton stays resident once created and initialisers run at link/class-load time. Runtime swizzling can dirty an otherwise-clean DATA_CONST. WWDC18 416@4:35exact timeWWDC18 416@3:50exact time

Limits, termination and fragmentation

  • statedEvery app has a footprint limit, it varies by device, and app extensions have a much lower limit. Exceeding it raises EXC_RESOURCE. Xcode 10 onward catches that exception and pauses the app so you can open the memory debugger immediately. WWDC18 416@7:40exact timeWWDC18 416@8:02exact timeWWDC18 416@10:11exact time
  • statedExceeding the limit means termination, and the next launch is a cold launch rather than a resume. WWDC21 10181@19:51exact time
  • statedQuery APIs: os_proc_available_memory (iOS/iPadOS/tvOS, in os/proc.h) for available memory, and proc_pid_rusage with RUSAGE_INFO_CURRENT for physical_footprint / lifetime_max_physical_footprint on any Apple platform. WWDC22 10106@6:39exact time
  • statedFragmentation = dirty pages that are not fully utilised. Freed objects leave non-contiguous holes; a large incoming allocation that cannot fit any single hole gets a fresh dirty page while the holes stay wasted. It is a footprint multiplier: the session’s worked example turns two dirty pages into four at 50% fragmentation. Those are that example’s page counts. WWDC21 10180@21:50exact timeWWDC21 10180@23:22exact time
  • statedRule of thumb given: aim for ~25% fragmentation or less; some fragmentation is inevitable. Mitigation: allocate objects with similar lifetimes near each other; autorelease pools help because they give the objects created inside them a common lifetime. WWDC21 10180@24:10exact time
  • demonstratedMeasure it with vmmap --summary, bottom section, % FRAG and dirty+swap frag size columns, per malloc zone. Long-running processes are especially prone — the session calls out long-running extensions specifically. WWDC21 10180@24:34exact time
A concrete workflow5
  1. Decide the question first: am I charged too much (footprint) or am I churning too much (Allocations)? They have different tools.
  2. For footprint, read dirty + swapped — in VM Tracker, or in vmmap --summary on an exported memgraph.
  3. If the heap is not the bulk, read the VM region names (CG image, IOSurface, mapped file) to find where it went.
  4. If the heap total looks fine but footprint is high on a long-running process, check % FRAG per malloc zone.
  5. For image-heavy screens, do the arithmetic before reaching for a tool: width × height × bytes-per-pixel.
Tips and tricks3
Interpretation traps5
macOS / iOS applicability

WWDC18 416 opens by saying that although it is focused on iOS, “a lot of what we’re covering will apply to other platforms as well”. Genuinely iOS-specific: no disk swap (compressor instead), the enforced footprint limit and EXC_RESOURCE, extension limits, and os_proc_available_memory. Genuinely cross-platform: dirty/clean accounting, 16 KB pages, fragmentation, proc_pid_rusage. macOS surfaces footprint in Activity Monitor. WWDC18 416@0:28exact timeWWDC22 10106@6:13exact time

Documentation clarifications for current tooling1
  • Which session supersedes which. WWDC18 416 is still the canonical model, and WWDC21 explicitly defers to it for depth; what is dated in it is the Xcode UI narration (“Xcode 10”, “Export Memgraph”) and the memory-gauge chrome. WWDC24 10173 is the current front door for heap work and explicitly scopes itself out of graphics memory and memory limits. WWDC21 10180@3:22exact timeWWDC24 10173@0:34exact time
This tool’s evidence gaps1
  • No session states a numeric footprint limit for any device or for extensions, and none uses the word “jetsam”. The sessions describe the behaviour (termination to reclaim memory, other processes jettisoned) without that term. No megabyte limit appears in this guide, because Apple does not publish one in this material.

VM Tracker and Virtual Memory Trace

Memory, energy, network & disk

Footprint across all VM regions — and the thinnest-evidenced tool in the guide.

The question it answers

How much memory is my process really charged for across all VM regions (not just the heap), and is the virtual-memory system itself doing expensive work on my behalf?

What the sessions show and say7
  • statedVM Tracker “provides separate tracks for dirty and swapped, which, in iOS, is compressed memory, and tells you some information about the resident size”. WWDC18 416@9:28exact time
  • statedVirtual Memory Trace “provides a deep view into the virtual memory system’s performance with regards to your app”, and the recommended entry point is its By Operation tab, which surfaces things like page cache hits and page zero-fills. Note the presenter says “VM Memory Trace”; the instrument is Virtual Memory Trace. WWDC18 416@9:50exact time
  • statedVM Tracker snapshots periodically rather than recording live, and is bundled in the Allocations template alongside Allocations. WWDC24 10173@6:14exact time
  • statedThe Game Memory template is the richest packaging: Allocations and Metal Resource Events for allocations with history, VM Tracker to record memory footprint, Virtual Memory Trace to record virtual memory activity, plus Metal Application and GPU instruments. WWDC22 10106@9:10exact time
  • demonstratedIn VM Tracker, Dirty Size is non-compressed dirty memory and Swapped Size is compressed-or-swapped; the Summary view lists VM regions, and memory-mapped assets show up under the mapped file region. WWDC22 10106@13:19exact timeWWDC22 10106@13:44exact time
  • statedThe reason you need VM Tracker at all: “allocations don’t always translate to memory footprint”. WWDC22 10106@13:19exact time
  • statedVM Tracker is also the tool that starts the WWDC18 dirty-memory investigation which then moves to vmmap on a memgraph. WWDC18 416@12:20exact time
A concrete workflow6
  1. Choose the Allocations template (VM Tracker included) or, for a Metal game, Game Memory.
  2. Enable VM Tracker only when you need footprint rather than heap churn — the WWDC24 presenter deliberately leaves it off when focusing on the heap.
  3. Record; read Dirty Size and Swapped Size as the footprint pair.
  4. Use the Summary view to attribute footprint to VM regions (mapped file, IOSurface, CG image, MALLOC_*).
  5. To go deeper than the trace allows, capture a memory graph and switch to footprint / vmmap.
  6. For VM-system activity (faults, zero-fills) rather than sizes, add Virtual Memory Trace and start from By Operation.
Tips and tricks2
  • VM Tracker’s periodic snapshotting means it shares the snapshot observer cost: it suspends the target app, and can stutter or hang it briefly. WWDC24 10173@32:11exact time
  • In a game, “All Anonymous VM” in the Allocations statistics view is where interesting possibly-dirty VM regions show up, distinct from “All Heap Allocations”. Allocations also “doesn’t include private Metal resources” — a real blind spot for graphics work. WWDC22 10106@10:23exact time
Interpretation traps3
  • inferredVM Tracker’s periodic snapshots are not a continuous record. A footprint excursion between snapshots can be missed entirely. Crew inference from the stated snapshot-based design; no session states this consequence outright. WWDC24 10173@6:14exact timeWWDC24 10173@32:11exact time
  • statedLarge virtual size is not a problem by itself. WWDC18 416@30:20exact time
  • statedClean, memory-mapped regions may be large and resident without counting toward footprint — but “excessive use will slow down the system and your game”, so “it doesn’t count” is not the same as “it’s free”. WWDC22 10106@5:24exact time
macOS / iOS applicability

Both instruments exist for both platforms; the interpretation of the swapped column differs (compressed on iOS, genuine swap possible on macOS).

Documentation clarifications for current tooling1
  • Both VM Tracker and Virtual Memory Trace are still present in current Xcode under those exact names — from the memory crew’s own local xctrace check, not an Apple page. No developer.apple.com/documentation/… page dedicated to either instrument was found.
This tool’s evidence gaps2
  • No session walks through reading a Virtual Memory Trace in any depth. This guide presents it as “exists, named in templates, not taught”.
  • VM faults as a scheduling concern are covered by System Trace instead — see that card.

Energy and power

Power Profiler · Energy Gauge · thermal state
Memory, energy, network & disk

Which subsystem is draining the battery, and whether your change helped.

The question it answers

Which device subsystem is draining the battery because of my app, and did my change actually help?

What the sessions show and say9
  • statedPower Profiler “lets you profile your app and record a power trace which can then be visualized in Instruments”. WWDC25 226@1:54exact timeWWDC25 2261:52named section only
  • demonstratedSetup shown: connect the iPhone wirelessly, Product › Profile, choose the Blank template, then add Power Profiler and CPU Profiler. WWDC25 226@3:11exact time
  • demonstratedThe first lane is system power usage, expressed as a percentage per hour (the demo’s selection read 10.5%/hr); beneath it are per-app power impact lanes for CPU, GPU, display and networking. 10.5%/hr is that demo’s number. WWDC25 226@4:04exact timeWWDC25 226@4:27exact time
  • statedPower impact is explicitly a score for prioritisation, not a physical unit: “This value is a score that lets you identify when unexpected power spikes occur, and prioritize debugging the highest impact subsystem.” WWDC25 226@5:20exact time
  • demonstratedWorked demo 1 (reproducible at the desk): CPU power impact averaged 1 before and 21 during the spike; the CPU Profiler call tree with Hide System Libraries pointed at VideoCardView; the cause was an eager VStack building every thumbnail up front; switching to LazyVStack brought the average to 4.3. 1, 21 and 4.3 are that demo’s numbers. WWDC25 226@5:20exact timeWWDC25 226@8:28exact time
  • demonstratedWorked demo 2 (not reproducible at a desk): an on-device trace from a colleague showed a periodic CPU-impact pattern; the Time Profiler named videoSuggestionsForLocation, which re-read and re-parsed a large JSON rules file on every location change — invisible at a stationary desk, obvious while commuting. Fix: load and parse lazily once, then cache. This is the best file-I/O-as-a-power-bug example in the corpus. WWDC25 226@12:59exact timeWWDC25 226@15:14exact time
  • demonstratedOn-device collection (Performance Trace). Turn on Developer Mode, Settings › Developer › Performance Trace, enable Power Profiler, then select your app; only apps installed by Xcode, TestFlight, or via the enterprise program can be monitored. Start/stop from a Control Center control; the resulting file opens directly in Instruments. WWDC25 226@10:55exact timeWWDC25 226@11:19exact timeWWDC25 226@11:43exact timeWWDC25 226@12:07exact time
  • statedIn on-device mode the Time Profiler runs at a lower sample rate to reduce the observer effect. WWDC25 226@12:31exact time
  • statedComparing two implementations. Profile approach 1, profile approach 2, compare — but thermals, device state, system pressure, and your app’s own state (data volume, enabled features) all move the numbers, so capture multiple runs of each approach under varying conditions and average. WWDC25 226@17:44exact timeWWDC25 226@18:10exact timeWWDC25 22616:19named section only

Documented facts that matter and are not in the video

Xcode Energy Gauge, subsystem costs and thermal state

  • statedThe Energy Gauge lives in the Debug navigator; it tracks CPU usage and flags high CPU utilisation (defined as CPU use greater than 20%) and CPU Wake Overhead. Expect spikes while drawing UI, processing network data or computing — but once the app is idle waiting for the user, CPU usage should be at or near zero. The >20% threshold is the most concrete, most quotable energy number in the whole corpus. WWDC21 10181@4:07exact timeWWDC21 10181@4:30exact time
  • statedWWDC25 places the gauge as the “instant feedback while coding” tier, with Instruments for deep dives, XCTest to catch issues early, and Organizer / MetricKit / App Store Connect API after shipping. WWDC25 226@18:35exact timeWWDC25 22618:25named section only
  • documentedCurrent documented behaviour: Debug navigator › Energy Impact shows average energy impact, a pie chart of contribution by power-use category, and a timeline of instantaneous energy use per category alongside the app’s life-cycle state and the device’s thermal condition. The documented category list: Audio, Networking, Processing (CPU and GPU), Display (screen), Bluetooth, Location, Camera, Torch, NFC, Other. DocsAnalyzing your app's battery use § Measure your running app's power use in XcodedocDocsAnalyzing your app's battery use § Review your app's energy use in Xcodedoc
  • statedDisplay / OLED: on OLED devices the colour of your UI directly affects display energy, measured as average pixel luminance (APL) — lighter UI means higher APL and more energy. The current Power Profiler doc gives the same lever as its remedy: reduce APL, support dark appearance. WWDC19 417@7:31exact time
  • statedNetworking is a high-energy subsystem, and connectivity quality itself drives the cost — time spent in poor connectivity matters, not just bytes. The top three battery levers named are CPU, Networking, and Location. WWDC19 417@8:14exact timeWWDC19 417@8:36exact timeWWDC21 10181@3:45exact time
  • statedEverything costs something. “Every time you’re hitting the file system, you’re using a little bit more power. Every time you’re sending off network requests, you’re using more power, more precise location stuff, using more power… If you can send out network requests half as often, that’s a free power win.” WWDC26 8003@14:53exact time
  • statedThe most common power mistake, per Apple’s own engineers: insufficient telemetry and instrumentation, leading teams to over-index on one customer report while field analytics show a bigger problem. “You always want to measure stuff before you optimize it.” Second most common: not accounting for app state — a database write that looks cheap at your desk can dominate power on a user’s much larger dataset. WWDC26 8003@12:27exact timeWWDC26 8003@13:17exact timeWWDC26 8003@13:41exact timeWWDC26 800312:09named section only
  • statedThermal states and the recommended response: nominal (no action), fair (proactively start energy-saving measures; the system begins pausing discretionary background work such as photos analysis), serious (system throttling; e.g. iCloud backup pauses), critical (stop using peripherals such as the camera). Register for thermal-state change notifications and read the state. WWDC19 422@20:06exact timeWWDC19 422@20:50exact timeWWDC19 422@22:42exact time
  • statedTesting thermal behaviour without heating the device: Xcode 11 added device conditions in the Devices and Simulators window, which make the device report an elevated thermal state while staying safe to use. The session explicitly names the bad practice this replaces — running a dummy CPU load to warm the device, discarding the first hour, then profiling. WWDC19 422@24:30exact timeWWDC19 422@24:52exact time
  • statedWWDC26 restates this as current practice (“with Xcode, you could actually use condition inducer feature to force your app in that state”) and gives concrete back-off strategies: request lighter network resources, simplify animations, reduce frame rate, reduce resolution. The system also reduces animation and display frame rates itself in heavier thermal states. WWDC26 8003@14:05exact timeWWDC26 8003@56:36exact timeWWDC26 8003@57:41exact time
A concrete workflow7
  1. Start from a symptom you can name: a high Energy Impact reading in the Debug navigator, an Organizer energy regression, or a user report.
  2. Reproducible at your desk? Product › Profile over a wireless connection, Blank template, add Power Profiler + CPU Profiler, reproduce.
  3. Read the system power lane for when, then the per-subsystem impact lanes for which: CPU, GPU, display, networking.
  4. Route on the winner: CPU → CPU Profiler; GPU → Metal debugger; display → APL / dark appearance; networking → HTTP Traffic + Network Connections.
  5. Not reproducible? Have someone who can reproduce it capture an on-device Performance Trace in Power Profiler mode and share the file.
  6. Fix, then re-measure — several runs of each approach, averaged, not one.
  7. For thermal defences, induce the thermal state with Xcode device conditions rather than warming the device.
Tips and tricks3
Interpretation traps5
macOS / iOS applicability

Power Profiler’s on-device Performance Trace mode is documented for iPhone (iOS 26+) and iPad (iPadOS 26+) only. The Energy Gauge, thermal-state API and device conditions are demonstrated on iOS. No session or doc in this cluster demonstrates macOS power profiling, and this guide does not imply one exists. DocsMeasuring your app's power use with Po… § Overviewdoc

Documentation clarifications for current tooling3
This tool’s evidence gaps3
  • The three classic energy sessions are unreachable (WWDC18 228, WWDC17 238, WWDC15 708) — independently re-confirmed in this task. Their titles circulate in search results but were never read, and nothing here is sourced from them.
  • Consequently the Energy Log instrument has no live session evidence at all; only the archived guide describes it.
  • Whether Power Profiler has any macOS equivalent or macOS support is unknown — the crew found no Apple material either way.

Network

HTTP Traffic · Network Connections · Network Link Conditioner
Memory, energy, network & disk

What actually went on the wire — and what never left the device.

The question it answers

What did my app actually put on the wire, when, on which connection, and why was it slow — or why did it not go to the network at all?

What the sessions show and say13
  • statedThe HTTP Traffic instrument shipped in Instruments 13, inside the Network template, and inspects traffic through the Apple networking stack. WWDC21 10212@0:09exact time
  • statedThe key architectural point: because it instruments the URL Loading System rather than sniffing packets, it covers HTTP/3 and VPN traffic, attributes traffic to processes, and reveals requests that hit the on-disk cache or that errored. A cache hit is invisible to a packet capture but visible here. WWDC21 10212@0:35exact time
  • statedTrack hierarchy: HTTP Traffic (count of concurrent tasks) → process (including background traffic) → URLSession (one per URLSession you create) → domain. WWDC21 10212@2:12exact timeWWDC21 10212@2:36exact time
  • statedTask vs transaction: a task is your API-level object — the interval starts when you call resume() and ends right before your completion block is called — and is made of one or more transactions, each a request/response pair. A redirect (apple.com → www.apple.com) is the canonical two-transaction task. WWDC21 10212@3:49exact timeWWDC21 10212@5:03exact time
  • statedThe five transaction states, in order: cache lookup, blocked, sending request, waiting for response, receiving response. WWDC21 10212@7:08exact timeWWDC21 10212@7:56exact timeDocsAnalyzing HTTP traffic with Instruments § View tasks and transactionsdoc
  • statedInterval labels are dense with information you would otherwise have to dig for: HTTP version, method, and whether an Authorization or Cookie header was sent; on the response side the status code, whether a cookie came back, and the content type. WWDC21 10212@6:20exact time
  • demonstratedColour encodes outcome: orange for a non-success at the HTTP level (401), green for success (201), grey for a cancelled task. WWDC21 10212@18:50exact timeWWDC21 10212@19:44exact time
  • demonstratedDemo 1 — head-of-line blocking. The initial screen took over 7 seconds; later tasks showed growing purple blocked states. Switching the domain track from “Tasks” to HTTP Transactions by Connection revealed six connections and a clear staircase: each transaction blocked until the previous one on the same connection finished. That is HTTP/1 head-of-line blocking; HTTP/2 multiplexes and needs no app change; the client picks the most modern version the server supports. After the server enabled HTTP/2: one connection, effectively no blocked time, under 3 seconds. 7 s, six connections and 3 s are that demo’s numbers. WWDC21 10212@12:18exact timeWWDC21 10212@16:50exact time
  • demonstratedDemo 2 — an expired cookie. Re-prompted for login; the label showed no cookie icon on the request. Walking back to the earlier successful transaction showed the server had sent Set-Cookie — but the Transactions detail view’s extended detail (full request and response headers) showed an expiry date in the past, so URLSession correctly refused to send it. A server-side bug, found from the client. WWDC21 10212@20:33exact timeWWDC21 10212@21:49exact time
  • demonstratedDemo 3 — a request that never left the device. A favourites refresh took “a couple of milliseconds”, too fast for a server round trip. In HTTP Transactions by Connection view the transaction was executed not on a connection but on Local Cache, and had no “waiting for response” state. Fix shown: set URLRequest.cachePolicy = .reloadRevalidatingCacheData so the server can answer 304. WWDC21 10212@23:56exact timeWWDC21 10212@26:10exact time
  • demonstratedDemo 4 — auditing a third-party SDK. An integrated xcframework created its own URLSession and issued POSTs to an analytics endpoint before the user tapped anything; the backtrace implicated CoreLocation, and the transaction’s request body contained location coordinates. The trace itself became the bug report. This is the strongest “audit what your app really does” material in the corpus. WWDC21 10212@29:28exact timeWWDC21 10212@31:09exact time
  • demonstratedBacktraces: selecting a task shows the backtrace of where resume() was called, which is how the demo navigated from a trace interval to the exact method in Xcode. WWDC21 10212@25:17exact timeDocsAnalyzing HTTP traffic with Instruments § View tasks and transactionsdoc
  • demonstratedExport to HAR for people without Instruments, via xctrace export with a HAR flag. HAR is JSON, so it is scriptable, but it loses Instruments-specific detail such as URLSessions and backtraces. The precise flag spelling is unverified — check xctrace export --help before copying a command. WWDC21 10212@31:55exact timeWWDC21 10212@32:42exact time

Network Link Conditioner and condition testing

  • statedmacOS: download and use the Network Link Conditioner preference pane. iOS: it is available from the developer settings menu on development devices, with presets for network types like 3G or EDGE. WWDC19 422@11:12exact timeWWDC19 422@11:37exact time
  • statedXcode 11 added device conditions in the Devices and Simulators window, including network link types, driven from the Mac. WWDC19 422@12:21exact time
  • statedThree constraints most people get wrong: (a) conditions are system-wide, so everything on the device reacts, not just your app; (b) the network UI indicators stay unchanged, so the status bar will lie to you; (c) an activated condition is a ceiling, not a floor — it cannot upgrade your real-world network. A gray status indicator appears, tapping it offers to stop, and disconnecting from Xcode stops the condition automatically. WWDC19 422@13:05exact timeWWDC19 422@13:28exact time
  • statedCurrent restatement: simulate conditions in Xcode’s Devices and Simulators window (e.g. the LTE profile), or use Network Link Conditioner in Developer Settings on an iPhone or iPad. Tech Talks 111378@6:17exact timeTech Talks 111378@6:43exact time
A concrete workflow9
  1. Name your objects first — set URLSession.sessionDescription and URLSessionTask.taskDescription, because those strings become the track and interval labels.
  2. Product › Profile → Network template (you get Network Connections and HTTP Traffic) → Record.
  3. Accept the capture-risk dialog — and read the trap about what the trace now contains.
  4. Option-click-drag to zoom to the traffic; expand the HTTP Traffic disclosure triangle; raise track height.
  5. Read shapes in Tasks view: long purple blocked runs, orange failures, grey cancellations, suspiciously short intervals.
  6. Switch the domain track to HTTP Transactions by Connection whenever the question is “why is this slow” or “did this actually go out” — it is the view that exposes both head-of-line blocking and Local Cache.
  7. Use the bottom detail list plus its filter field to find requests by substring; selecting there moves the time cursor in the track view.
  8. Select a transaction and read the extended detail (headers and bodies) for correctness and privacy questions; select a task and read the resume backtrace to get to code.
  9. Export HAR to hand off.
Tips and tricks4
Interpretation traps6
macOS / iOS applicability

The documentation scopes the HTTP Traffic instrument to “iOS, iPadOS, watchOS, tvOS, and macOS apps”; the session says it “just works on all Apple devices”. Network Link Conditioner is split: a separate downloadable preference pane on macOS, built into developer settings on iOS. DocsAnalyzing HTTP traffic with Instruments § OverviewdocWWDC21 10212@0:35exact timeWWDC19 422@11:12exact timeWWDC19 422@11:37exact time

Documentation clarifications for current tooling2
  • Current and maintained; it links back to session 10212 itself, which is a strong signal that the 2021 session is still the canonical teaching material. DocsAnalyzing HTTP traffic with Instrumentsdoc
  • The Network template and both the HTTP Traffic and Network Connections instruments are present in current Xcode under those names — memory crew’s local xctrace check, not an Apple page.
This tool’s evidence gaps2
  • The session never demonstrates the older Network Connections instrument, only names it as the other track in the template. No session in this cluster teaches it, and this guide does not pretend one exists.
  • xctrace export’s exact current HAR flag spelling was not verified. Check xctrace export --help before publishing a copy-pasteable command.

Disk and file activity

File Activity template · Filesystem Activity / Suggestions / Disk Usage
Memory, energy, network & disk

One line of code, twelve I/O operations.

The question it answers

What is my app actually writing, how many physical I/O operations does one innocent-looking line of code cause, and which writes can I batch away?

What the sessions show and say8
  • statedThe stakes, as Apple states them. “Writing to disk can wear out my users’ NAND, which will lead to poor device health. Writes also take a lot of time and can lead to poor user experience and slow performance if done frequently, so it’s important to batch these writes.” Unchecked disk writes are also “a common cause for annoying onscreen hangs and UI hitches” and “can eat up the battery life quickly”. WWDC21 10181@12:50exact timeWWDC21 10087@8:03exact time
  • demonstratedThe headline demo. Reading a plist, updating one property and writing it back produces one read and three writes plus an fsync — not one write. The extra writes are file-system metadata. The fuller version: creating, reading and modifying a plist causes 12 separate I/O operations for what is roughly four lines of code, and every NSDictionary.write(toFile:atomically:) ends in an fsync. 1 / 3 / 12 are that session’s counts. WWDC19 419@5:29exact timeWWDC19 419@5:52exact timeWWDC19 419@14:23exact time
  • statedWhat Instruments 11 changed in the File Activity instrument: support for all Apple devices; tracing the whole system, not just your process, so you can see interactions; an explicit logical vs physical I/O distinction; and automated reasoning covering (a) excessive physical writes, (b) failed I/O-related system calls — which matter for correctness, not just performance — and (c) suboptimal caching, i.e. code that is not making good use of the OS cache. WWDC19 419@25:51exact timeWWDC19 419@27:43exact time
  • demonstratedThe tracks you get: a file system suggestions track (where the anti-pattern notifications live), a file system activity track broken into logical reads and logical writes with call counts and callers, and a disk usage track which is “where all the physical I/O information will live”. WWDC19 419@28:04exact timeWWDC19 419@29:09exact time
  • demonstratedSuggestions count as a scalar progress metric. The demo’s SQLite workload went 12 → 3 → 0 suggestions as it moved from open/close-per-operation to better patterns to WAL-mode journalling, with the 12 being “excessive physical writes” notifications. With WAL mode, logical I/O no longer had a matching physical I/O and only a single fsync call remained versus delete-mode journalling. Those are that demo’s numbers. WWDC19 419@30:39exact timeWWDC19 419@31:44exact timeWWDC19 419@32:32exact timeWWDC19 419@33:16exact time
  • demonstratedAttributing writes to code in the field. The Organizer’s disk writes reports are collected from consenting devices, contain the stack trace that led to the writes, and are broken into signatures with writes tracked per signature and sorted by total; in the demo the top signature accounted for 67% of writes. These reports are generated when an app writes more than 1 GB in a 24-hour period. 67% is that demo’s number. WWDC21 10087@8:50exact timeWWDC21 10087@9:13exact timeWWDC21 10181@14:50exact time
  • demonstratedInsights (Xcode 13): Apple maintains a repository of known disk-write anti-patterns; matching reports are flagged with an optimisation suggestion and a documentation link. The demo’s suggestion was “add an index”. WWDC21 10087@10:26exact timeWWDC21 10087@10:50exact time
  • demonstratedFile Activity as the validator. After adding the index, writes attributable to that function went from about 180 MB of writes to temporary files, carrying 780 ms of latency, to zero. The presenter calls File Activity “the perfect tool to validate my fix”. 180 MB and 780 ms are that demo’s numbers. WWDC21 10087@11:13exact timeWWDC21 10087@11:38exact time

The flush-API hierarchy, where most “why so many writes” answers bottom out

  • statedfsync moves data from the OS cache to the disk cache only. It does not guarantee the data reaches permanent storage, and it does not guarantee write ordering. It is expensive if overused, because while data sat in the OS cache the system could absorb repeated overwrites; once flushed it cannot. The OS does this periodically anyway, so calling it manually is often unnecessary. WWDC19 419@11:01exact time
  • statedfcntl(F_FULLFSYNC) pushes the drive’s disk cache to permanent storage — but flushes all data in the disk cache, not just yours, so it is expensive. WWDC19 419@12:09exact time
  • statedfcntl(F_BARRIERFSYNC) is the recommended alternative when your real requirement is I/O ordering: “an fsync with a barrier”, telling the Apple SSD to execute everything received before the barrier first. Explicit guidance: “if you’re concerned about I/O ordering, please use F_BARRIERFSYNC and not F_FULLFSYNC.” WWDC19 419@13:17exact time
  • statedTemporary files done right: create the file, keep it open and unlinked, and do not call fsync — this gives the file system the hints it needs to keep the data in the OS cache as long as possible rather than writing it out. WWDC19 419@9:12exact time
  • statedSerialised data files (plists) scale poorly, are easy to misuse precisely because they are easy to use, are metadata-intensive because the whole file is replaced on every change, and are “very much not meant to be a database replacement”. WWDC19 419@14:02exact time
  • statedConcrete reduction tactics: batch writes, use Core Data for frequently-changing data, and avoid rapid file creation and deletion. For CI gating, pass an XCTStorageMetric instance to the measure API and set a baseline so the test fails when the block writes more than expected. WWDC21 10181@13:37exact timeWWDC21 10181@14:01exact time
A concrete workflow6
  1. Product › Profile → File Activity template → Record; exercise the write-heavy flow.
  2. Read the file system suggestions track first — it is a count, and the detail view groups the notifications by kind (e.g. “excessive physical writes”). Treat the count as your score.
  3. Read file system activity for logical reads/writes, call counts, and callers.
  4. Read disk usage for the physical side, and compare: logical activity with no matching physical activity means the OS cache is absorbing it, which is the good case.
  5. Change one thing, re-record, and watch the suggestion count move.
  6. For shipped versions, go to the Organizer’s disk-writes reports, read the top signatures and any insight, fix, then validate with File Activity as in step 2.
Tips and tricks4
  • Record a deliberate baseline trace of the bad pattern before fixing anything, so the suggestion count and I/O counts have something to be compared against — the WWDC19 demo does this explicitly. WWDC19 419@29:09exact time
  • The logical/physical gap is the signal, not either number alone. WWDC19 419@33:16exact time
  • Full VACUUM versus incremental vacuum is measurably different in the instrument: the demo’s full vacuum produced 27 I/O operations totalling 168 KB per delete. That demo’s numbers. WWDC19 419@35:47exact time
  • System-wide tracing is available, so you can see how your app interacts with the rest of the system rather than only its own calls. WWDC19 419@26:15exact time
Interpretation traps6
  • demonstrated“One write” in your code is not one write on the device. The 1-read / 3-write / fsync result is the whole point of the session. WWDC19 419@5:29exact time
  • statedLogical writes ≠ physical writes, and the metric Apple tracks in the field is the logical one. “this year we’re going to be focusing on disk logical writes”. So an app can look bad on the field metric while the OS cache absorbs much of it, and vice versa. Crew inference: use File Activity to see both, and do not reason about NAND wear from the logical number alone. WWDC19 417@9:43exact time
  • statedfsync is not durability. Believing it reaches permanent storage is the most consequential misreading in this whole area. WWDC19 419@11:01exact time
  • statedF_FULLFSYNC punishes the whole system, not just your file. WWDC19 419@12:09exact time
  • demonstratedZero suggestions is not zero I/O. The suggestions track flags known anti-patterns; the demo’s WAL-mode run had zero suggestions and still performed I/O. WWDC19 419@32:32exact time
  • statedA disk-writes report exists only past a threshold. No report does not mean no problem — it may mean you did not cross 1 GB / 24 h. WWDC21 10181@14:50exact time
macOS / iOS applicability

The Instruments 11 rework explicitly added “support for all Apple devices… the same uniform profiling experience across your iOS device, Mac, Watch, TV, etcetera”. The NAND-wear framing and the Organizer disk-writes reports are presented in an iOS context. WWDC19 419@25:51exact time

Documentation clarifications for current tooling1
  • Naming has drifted. The template is still called File Activity, but the instruments inside it are now listed as Filesystem Activity, Filesystem Suggestions, Disk Usage and Disk I/O Latency — the session’s spoken “file system…” tracks map onto instruments whose current names use “Filesystem” as one word. From the memory crew’s local xctrace check, not an Apple page. No current documentation page dedicated to the File Activity instrument was found.
This tool’s evidence gaps2
  • WWDC19 419 is a 2019 session and its Instruments UI narration is seven years old. No newer session re-teaches File Activity, so treat it as “still the only substantial teaching material, with dated UI”.
  • The F_BARRIERFSYNC guidance was not re-verified against a current Apple documentation page.

05App & framework level

What you reach for before or instead of a raw trace: SwiftUI diagnostics, launch, the gauges, and the field.

The SwiftUI instrument

Instruments 26 and later
App & framework level

The only tool that answers why a view body ran at all.

The question it answers

Which of my view bodies are slow, and what caused them to run at all? It is the only tool that answers the second half.

What the sessions show and say10
A concrete workflow6
  1. Install Xcode 26 or later and update the device to the latest OS release; both are required to record SwiftUI traces.
  2. Press Command-I. Xcode compiles in Release and launches Instruments. Choose the SwiftUI template.
  3. Record, exercise the interaction you suspect, stop.
  4. If the problem is slow updates: inspect Long View Body Updates first, filter to its summary, pick a red entry, “Set Inspection Range and Zoom”, then read the Time Profiler track for that range.
  5. If the problem is too many updates: use the Update Groups lane to find groups that stay active without containing long updates, then “Show Causes” and read the Cause & Effect graph.
  6. Re-record after each fix. The docs are explicit that the graph shows only one changing property per edge, so a second recording is needed to see whether a different property now drives the same update.
Tips and tricks6
  • If CPU spikes while the Update Groups lane is empty, the problem is not SwiftUI — go to Time Profiler or the Hangs/Hitches instruments instead. This is the single cheapest triage signal the instrument offers. WWDC25 306@3:00exact time
  • A single long update may not contain enough Time Profiler samples to identify the code. Repeat the interaction while recording, then control-click MySwiftUIView.body in the detail view and choose “Show Calls Made by MySwiftUIView.body” to aggregate every instance. Reset with Callers/Callees → Clear Selection. DocsUnderstanding and improving SwiftUI pe…doc
  • Blue nodes are your code, grey nodes are the system. A graph that runs blue → grey → blue means your app is generating an event that your own app then has to respond to, mediated by SwiftUI — a strong signal to cut the event’s frequency at the source. DocsUnderstanding and improving SwiftUI pe…doc
  • When reading an update group, you may need to drag the start of the inspection range earlier by hand, because the events that caused the group often occur before the group begins. DocsUnderstanding and improving SwiftUI pe…doc
  • It is a debugging tool, not only an optimisation tool: if you can reproduce an issue and capture a trace, the graph tells you exactly which input caused a re-evaluation — often simpler than Self._printChanges(). It can also render the view hierarchy as a tree and draw a flame graph weighted by time spent per view. WWDC26 8006@31:24exact timeWWDC26 8006@32:30exact time
  • A high-yield habit from the SwiftUI team: look for views updating that are not on screen or shouldn’t update at that moment. An engineer describes finding the TV app updating sliders when no sliders were visible. Either the app has a bug or your model of it does — both worth fixing. WWDC26 8006@32:49exact time
Interpretation traps6
  • documented“Long” has a precise, documented meaning the video never states. The Long View Body Updates lane draws orange above 500 microseconds and red above 1000 microseconds. A reader who assumes “long” means tens of milliseconds will dismiss real findings. DocsUnderstanding and improving SwiftUI pe…doc
  • demonstratedLong updates immediately after launch are usually normal. In the demo, two long updates survived the fix; the presenter explains they occur as the app prepares its first frame and that it is not uncommon for updates right after launch to take longer. Do not chase them first. WWDC25 306@15:02exact time
  • documentedA short update is not a cheap update. Many short updates in one group can cost more than a single long one, and the Long-update lanes will not show them at all — only the Update Groups lane will. DocsUnderstanding and improving SwiftUI pe…doc
  • documentedA dimmed node means the body did not run. The view was notified (typically an environment change) but SwiftUI skipped it. Counting dimmed nodes as re-evaluations overstates the problem. DocsUnderstanding and improving SwiftUI pe…doc
  • documentedThe graph deliberately duplicates nodes. Instruments repeats the same node or edge in several places to keep the layout readable; clicking one highlights all instances. Reading duplicates as separate events inflates your count. DocsUnderstanding and improving SwiftUI pe…doc
  • inferredBecause the graph surfaces only one changing property per edge, an apparent single-cause result can hide a second cause that only becomes visible once the first is fixed. Treat the first graph as a hypothesis, not a complete causal account. Crew inference.
macOS / iOS applicability

Applies to both. The lane the WWDC25 video calls Long Representable Updates is documented as covering AppKit views hosted in SwiftUI on macOS as well as UIKit views on iOS, iPadOS and Mac Catalyst. The WWDC25 demo itself is an iOS app. DocsUnderstanding and improving SwiftUI pe…doc

Documentation clarifications for current tooling3
  • The single most important companion to WWDC25 306. It supplies the microsecond thresholds, the aggregate-call-tree filtering technique, the blue/grey node semantics, and design rules the video omits entirely. DocsUnderstanding and improving SwiftUI pe…doc
  • Lane naming has drifted. The video says Long Representable Updates; the current doc calls the same lane Long Platform View Updates. Use the doc name as current and the video name as what you will hear. WWDC25 306@3:29exact timeDocsUnderstanding and improving SwiftUI pe…doc
  • The doc adds guidance found in no session the crew could reach: avoid storing closures in views, because a stored closure capturing self forces recalculation whenever any view property changes; call an incoming view-building closure in the initializer and store only its result; do not mark it @escaping. Action closures (Button) and parameterised closures (ForEach) are exempt. It also recommends onGeometryChange(for:of:action:) callers test whether the change exceeds a threshold before triggering layout work. DocsUnderstanding and improving SwiftUI pe…doc
This tool’s evidence gaps2
  • No locator was obtainable for any statement of what Instruments 27 changes about the SwiftUI instrument specifically; the WWDC26 labs discuss it as an established tool rather than announcing changes.
  • Whether the 500 µs / 1000 µs thresholds are configurable is not stated in either the video or the doc.

Self._printChanges() and Self._logChanges()

App & framework level

Why did this body run, answered inline, without launching Instruments.

The question it answers

Why did this specific view’s body run just now? — answered inline, with no trace.

What the sessions show and say6
A concrete workflow6
  1. Add let _ = Self._printChanges() as the first line of the suspect view’s body.
  2. Reproduce the interaction and read the console.
  3. Interpret the output: a named property means that property changed; @self means the view’s own value changed.
  4. On @self, move up one level — the parent changed the value it passed down. Repeat there.
  5. Narrow the child’s input to the smallest thing it actually needs.
  6. Delete the call before shipping.
Tips and tricks4
  • You do not have to edit the file. Break in the view’s body and call Self._printChanges from the LLDB console with expression. This avoids the edit-rebuild cycle entirely and removes the risk of shipping the call — the single best trick in this section, and easy to miss because the session shows it before the in-code form. WWDC23 10160@7:46exact time
  • The @self decoding rule, from the SwiftUI team: @self just means the value of that view changed, and the right response is to crawl up to the next level and ask why the parent changed. Engineers acknowledge this output “can feel like a mystery”. WWDC26 8006@31:10exact time
  • Complementary trick with no API at all: apply a random background or border colour to a view. If resizing or scrolling turns the screen into, in the engineers’ phrase, a disco dance party, you have a lot of invalidation. It visualises where and how often without any instrumentation, and it is the team’s own habit. WWDC26 8006@30:18exact timeWWDC26 8006@48:02exact time
  • The team’s stated workflow for a re-render storm is “all of the above” — _printChanges, the random-colour trick, and the SwiftUI instrument, chosen by situation rather than ranked. WWDC26 8006@30:07exact time
Interpretation traps4
  • statedIt is explicitly a best-effort explanation, in the session’s own words — not an authoritative account of why the body ran. Treat a surprising result as a lead, not a verdict. WWDC23 10160@7:53exact time
  • inferredIt tells you a view updated; it does not tell you the update was expensive. Pairing it with the instrument’s timing is what separates “updates often” from “costs anything”. It also only reports for views you have annotated, so it cannot find the view you failed to suspect — the instrument’s summary can. Crew reasoning.
  • statedScoping dependencies is not free advice. The session warns the extract-a-narrower-view technique works great for smaller views but to be careful with very large structs — not every dependency deserves to be scoped, and it says to use your best judgement. WWDC23 10160@10:01exact timeWWDC23 10160@10:05exact time
  • inferredBecause it is an underscored API with no compatibility guarantee, its output format may change between releases; do not build tooling that parses it. Crew inference.
macOS / iOS applicability

Both — it is a SwiftUI-framework facility with no platform-specific behaviour stated in any source the crew read.

Documentation clarifications for current tooling1
  • No Apple documentation page exists for Self._printChanges() or Self._logChanges(). Both are underscored and undocumented; the WWDC sessions and labs are the only official evidence. Treat that as a deliberate “use while debugging, do not depend on it” signal.
This tool’s evidence gaps1
  • The exact signature, availability and behaviour of _logChanges are not documented anywhere reachable — the WWDC26 lab mention is the only official reference, and it does not show code. Which OS log subsystem/category it writes to is unverified.

SwiftUI list and lazy-stack structure

App & framework level

Scroll cost that no individual body explains.

The question it answers

Why is my scrolling list slow or janky when no individual view body is expensive? The answer is usually structural — how many subviews each element resolves to, not what they do.

What the sessions show and say7
  • demonstratedThe 2023 rule: row count = number of elements × number of views per element, and the views-per-element count must be constant or SwiftUI has to build all views up front to determine identifiers. SwiftUI gathers all IDs of List and Table contents eagerly, so cheap identifier creation translates directly into faster load and update. Named anti-patterns: a conditional inside ForEach (0-or-1 views per element), AnyView (unknown count), and inline .filter in the ForEach argument (constant count, but a linear filter on every update; filter at the model layer instead). A nested ForEach inside Section is explicitly fine — SwiftUI understands that construction. WWDC23 10160@13:24exact time
  • demonstratedThe 2026 session deepens this substantially. A view struct does not map one-to-one to the subviews a lazy stack loads: a StepView whose body contains two top-level views not wrapped in a layout resolves to two subviews that the LazyVStack evaluates and loads separately. WWDC26 321@10:09exact timeWWDC26 321@10:34exact time
  • statedA dynamic subview count keeps the parent view alive longer than expected, because a lazy stack addresses visible subviews by index and must retain earlier views in case the condition changes and shifts the indices. A change to an unrelated environment value can then cause body evaluations for views scrolled off screen, and the stack will not release their state. WWDC26 321@11:23exact timeWWDC26 321@12:02exact timeWWDC26 321@12:10exact time
  • statedUnwrapping an optional in a view body has the same effect as a conditional — a trap that looks nothing like an if. WWDC26 321@12:35exact time
  • statedPrefetching, an internal mechanism: while scrolling, a lazy stack checks whether there is time before the frame deadline and does part of a not-yet-visible view’s work early — possibly evaluating body and layout — so the work is spread across frames rather than spiking when the view appears. WWDC26 321@13:15exact timeWWDC26 321@14:34exact timeWWDC26 321@14:43exact time
  • statedConsequence: body runs at one point, onAppear a little later. If the scroll direction reverses, the body may run as part of prefetching and onAppear may never be called. WWDC26 321@15:07exact timeWWDC26 321@15:16exact time
  • statedViews scrolled off screen are retained for a number of updates in case they scroll back; when finally deleted, their @State is deleted with them. WWDC26 321@16:59exact timeWWDC26 321@17:11exact time
A concrete workflow6
  1. Before profiling, read the ForEach body structurally: does each element resolve to exactly one subview, always?
  2. Remove conditionals, optional unwrapping and AnyView from leaf views inside ForEach. Move the condition into the data query (a Predicate on a SwiftData Query).
  3. Check that the element view’s body has a single top-level view (wrap in a layout if not) if you want one-subview-per-element resolution.
  4. Move anything that must survive scrolling out of @State into a model object or a binding to an outer view.
  5. Set the view up in its initializer, not onAppear, so prefetched work is not thrown away.
  6. Only then profile with the SwiftUI instrument.
Tips and tricks6
Interpretation traps4
  • demonstratedThe 2023 “constant views per element” rule is correct but incomplete. 2026 shows the count is determined by resolution, so a two-view body silently doubles the subview count even with no conditional present. WWDC26 321@10:34exact time
  • statedonAppear is not a reliable “view became visible” hook in a lazy stack — it can be skipped entirely on reversed scroll. Analytics or data-loading keyed on it will under-count. WWDC26 321@15:16exact time
  • statedState loss on scroll-away is by design, not a bug. Selection or highlight state kept in @State inside a row will silently reset. WWDC26 321@17:11exact time
  • inferredBecause prefetching is time-budget-driven, a device under load prefetches less, so a lazy-stack problem can look device-dependent and intermittent. Reproduce on the slowest supported device before concluding a fix worked. Crew inference.
macOS / iOS applicability

The lazy-stack mechanics are SwiftUI framework behaviour and apply to both; the 2026 demo is an iOS app. The 2023 Table guidance is most relevant on macOS, where Table is the common construct. A lab attendee asked directly whether macOS 27 brings performance improvements for very large lists and tables; the panel did not confirm any and answered with general best practices instead, so this guide does not imply a macOS 27 improvement exists. WWDC26 8003@37:40exact time

Documentation clarifications for current tooling2
This tool’s evidence gaps1
  • Whether the 2023 Table/TableRow identity changes (iOS 17) still hold as described in the 2026 releases was not verified; no 2026 source the crew read revisits them.

App Launch template

App & framework level

Where the milliseconds between tap and first frame go.

The question it answers

Where do the milliseconds between tap and first frame actually go?

What the sessions show and say5
  • statedThe goal: render the first frame within 400 ms, so pixels are on screen during the launch animation and the app is interactive by the time the animation completes. This is the one figure in this card presented as a goal rather than a demo measurement. WWDC19 423@5:02exact timeWWDC19 423@5:08exact time
  • statedSix phases, covering system initialization → app initialization → view creation and layout, plus an optional extended phase for asynchronous data loading: dyld (the dynamic linker loads shared libraries and frameworks), libSystemInit, static runtime initialization, UIKit initialization (the system instantiates UIApplication and the app delegate; mostly system work unless you subclass UIApplication or work in delegate initializers), application initialization (described as where developers can likely have the biggest impact), and first frame render. WWDC19 423@6:24exact timeWWDC19 423@6:42exact timeWWDC19 423@7:58exact timeWWDC19 423@8:37exact timeWWDC19 423@9:08exact timeWWDC19 423@9:32exact timeWWDC19 423@11:46exact time
  • statedCold vs warm launch definitions, and the recommendation to measure warm launches for consistency. WWDC19 423@3:35exact timeWWDC19 423@4:07exact time
  • demonstratedThe demo’s three findings and fixes, on an app called Star Searcher: a framework’s +load static initializer costing 375 ms and removed by dropping the framework; a priority inversion where the main thread waited 754 ms on a background-QoS worker, fixed by restoring priority propagation and loading only the first 20 rows; and 882 ms of speculative DetailViewController pre-warming in cellForRowAt, deferred to didSelectRowAt. The demo reports going from about 2.5 seconds to just over 300 ms. All of these are that demo’s numbers, on that app, on 2019 hardware — they are not targets. The crew recorded no per-claim locator for these figures; the investigation runs from the mark cited here. WWDC19 423@22:03exact time
  • statedThe three-principle frame: minimize, prioritize, optimize. No locator was recorded for this phrasing. WWDC19 423no locatorno locator
A concrete workflow5
  1. Use the Profile scheme so the app is built in Release with optimizations.
  2. In Instruments choose the App Launch template; it gathers a time profile and a thread-state trace.
  3. Expand and pin the main thread plus any worker doing substantial work; read thread state by colour (blocked / runnable / preempted / running).
  4. Attribute the cost to a phase, then use the inspector’s aggregated stack trace to find the responsible code.
  5. Verify with an XCTest launch measurement and a baseline.
Tips and tricks6
  • Time on the main thread that is running or preempted is time it cannot draw the first frame or respond to input — so a thread-state trace, not just a CPU profile, is what exposes launch blocking. DocsReducing your app's launch timedoc
  • Profile launch under four different conditions the doc enumerates: after first unlock following boot; after force-quit; after opening other apps (partial eviction); and after using a memory-heavy app. This is the documented way to sample the warm–cold spectrum rather than guessing. DocsReducing your app's launch timedoc
  • The dyld Activity instrument specifically measures time spent running static initializers. If you suspect pre-main() cost, this is the direct measurement rather than inference from the launch phases. The doc enumerates exactly what runs before main(): C++ static constructors, Objective-C +load methods, __attribute__((constructor)) functions, and anything in __DATA,__mod_init_func. DocsReducing your app's launch timedoc
  • Mergeable dynamic libraries (Xcode 15 and later) give launch times similar to static linking in release builds while keeping dynamic-linking build times in debug. DocsReducing your app's launch timedoc
  • For a different view, profile with the Time Profile template: its App Life Cycle timeline divides launch into process initialization, UIKit initialization, UIKit initial scene rendering, and initial frame rendering. DocsReducing your app's launch timedoc
  • Work after the first frame does not count toward the launch metric but does count for the user. Instrument it yourself with an OSLog using the pointsOfInterest category and os_signpost around your preparation tasks. DocsReducing your app's launch timedoc
Interpretation traps5
  • documentedThe launch metric stops at first frame, not at usability. An app that draws a spinner “wins” the metric while the user still waits. The doc states this explicitly and it is the most consequential trap in this tool. DocsReducing your app's launch timedoc
  • demonstratedThe 2019 demo’s numbers are not benchmarks. 375 ms, 754 ms, 882 ms and 2.5 s → 300 ms describe one sample app on 2019 hardware. Only the 400 ms first-frame figure is presented as a goal. WWDC19 423@5:02exact time
  • documentedDrawing the launch storyboard does not stop the launch-time counter. DocsReducing your app's launch timedoc
  • demonstratedProfiling overhead distorts phase durations — the session’s own trace shows dyld at 6 ms CPU against 149 ms wall clock. Compare like with like. No locator was recorded for this observation. WWDC19 423no locatorno locator
  • inferredWWDC19 423 predates SwiftUI’s App lifecycle and describes UIApplicationDelegate / UIScene callbacks. The phase model still holds, but a reader on a pure SwiftUI app will not find the named delegate methods in their code. Crew inference.
macOS / iOS applicability

The session is iOS-only in its specifics (splash screen, watchdog termination, UIKit, UIScene). The documentation covers macOS separately and differently: on macOS the system does not terminate your process as part of normal use, and an activation may instead require bringing memory back from the compressor or swap. The Organizer Launch Time pane is documented for iOS apps. DocsReducing your app's launch timedoc

Documentation clarifications for current tooling1
  • Materially more current than WWDC19 423 and the spine any launch work should use: it adds mergeable libraries, the four test conditions, the dyld Activity instrument, the App Life Cycle timeline, macOS behaviour, and Xcode’s Generate Recommendations button on a launch report. DocsReducing your app's launch timedoc
This tool’s evidence gaps2
  • The two classic launch sessions are gone. WWDC16 406 (“Optimizing App Startup Time”) and WWDC17 413 (“App Startup Time: Past, Present, and Future”) both return HTTP 200 but redirect to the year index — independently re-confirmed in this task. They are widely cited by third parties; nothing may be attributed to them.
  • WWDC22 110362 and 110363 were not mined in depth, so no claim is made about linker-level or runtime-level launch improvements beyond what the docs state.

Xcode debug gauges

Debug navigator · Energy Impact
App & framework level

A zero-setup smoke alarm, not a diagnostic.

The question it answers

Is anything obviously wrong right now, while I’m just running the app?

What the sessions show and say5
  • statedThe debug navigator gives a high-level overview of the CPU, memory and energy subsystems; when you need to dig into details, Instruments is the tool. This is the framing Apple itself uses — gauges first, Instruments second. WWDC19 417@12:41exact timeWWDC19 417@12:48exact time
  • statedThe Energy Gauge shows regions of high CPU utilization and CPU Wake Overhead. Two definitions are given outright: high CPU utilization is CPU use greater than 20%, and CPU wake overhead is where the CPU wakes from idle and incurs an energy cost. WWDC21 10181@4:18exact timeWWDC21 10181@4:30exact timeWWDC21 10181@4:35exact time
  • statedThe reading heuristic: a CPU spike is expected while drawing UI, processing network data or calculating, but once those finish and the app is waiting for the user, CPU should be at or near zero. WWDC21 10181@4:42exact timeWWDC21 10181@4:50exact time
  • statedYou can jump straight from the gauge to Instruments by clicking Time Profile, giving thermal state, CPU usage and call stacks for the profiled duration. WWDC21 10181@5:00exact time
  • documentedThe archived Energy Efficiency Guide names the Energy Impact display’s components: blue bars for the app’s own cost and red bars for overhead (system resources powered up on the app’s behalf), plus per-category indicators for CPU, Network, Location, GPU and Background. Archived, last updated 2016. DocsEnergy Efficiency Guide for iOS Apps: …doc
A concrete workflow4
  1. Build and run from Xcode, open the Debug navigator.
  2. Watch the gauge while exercising the feature, and again while doing nothing.
  3. Treat sustained activity during idle as the signal worth chasing.
  4. Click through to Instruments (Time Profile, or Power Profiler for energy) for anything real.
Tips and tricks3
  • The “idle should be near zero” test is the gauges’ highest-value use — it needs no baseline, no trace and no instrumentation, and it catches over-invalidation, stray timers and background work that a feature-focused profile would miss. WWDC21 10181@4:50exact time
  • The red/blue split in the Energy Impact display separates your work from the overhead you caused — useful because a small amount of your own work can power up an expensive subsystem. Archived source. DocsEnergy Efficiency Guide for iOS Apps: …doc
  • When the gauge says “high” and you cannot see why, the engineers’ recommended next step is Power Profiler in untethered mode: record a power trace on the device while disconnected, reproduce a realistic scenario, then open it in Instruments. Being tethered changes the behaviour you are trying to observe. A power trace also breaks energy down by subsystem (CPU, GPU, display, networking), which the gauge does not. WWDC26 8003@18:55exact timeWWDC26 8003@19:08exact timeWWDC26 8003@19:31exact time
Interpretation traps3
  • statedA “high” energy reading does not localise the cause, and a foreground SwiftUI/UIKit screen showing high battery use is frequently background work. The lab’s answer to exactly this question is that SwiftUI and UIKit compute should show up as foreground energy, so background drain points at scheduled background tasks or location use instead. Reading the gauge as a verdict on the visible screen is the specific mistake. WWDC26 8003@18:37exact timeWWDC26 8003@18:43exact time
  • inferredYou are measuring a debug build with a debugger attached. Numbers are not comparable to shipping behaviour, and it is a category error to use them as a performance baseline. The gauges also show current and recent activity only — no history, no percentiles, no comparison across versions. That is the Organizer’s job. Crew reasoning.
  • inferredThe 20% high-utilization figure comes from a 2021 session describing the energy gauge’s own banding; the crew would not treat it as a universal CPU budget for an app. WWDC21 10181@4:30exact time
macOS / iOS applicability

The Debug navigator gauges exist for both platforms. The detailed component documentation the crew could verify is the iOS Energy Efficiency Guide; a separate macOS guide exists in the same archive but its contents were not verified. The GPU indicator is documented as reported only on iPhone 5s and later with iOS 10 or later — a detail whose age is itself a warning.

Documentation clarifications for current tooling2
This tool’s evidence gaps1
  • No current (non-archived) Apple page describes what each gauge displays, and no session from 2022 onward walks through the gauges.

Thread Performance Checker

App & framework level

Priority inversions and main-thread I/O, surfaced without profiling.

The question it answers

Are there priority inversions or main-thread I/O in my app that I haven’t noticed? — surfaced as Xcode issues during ordinary development, with no profiling session.

What the sessions show and say4
  • demonstratedIt “notifies you in the Xcode Issue Navigator when it detects priority inversions and non-UI work on the main thread of your app, both of which are common causes of hangs” — and it works “while debugging your app without actively tracing it”. Enabled from the Diagnostics section of the scheme. WWDC22 10082@3:55exact timeWWDC22 10082@4:49exact timeWWDC22 10082@5:30exact time
  • demonstratedPriority inversion, in the demo’s terms: “a higher priority thread was attempting to be synchronized with a lower priority thread. This may indicate the hang we are noticing is caused by the main thread waiting on different lower-priority threads.” WWDC22 10082@5:13exact time
  • demonstratedIt now also surfaces in tests (Xcode 26): “Tests can now surface framework runtime issues as well as call out threading problems using the Thread Performance Checker.” WWDC25 247@35:37exact time
  • documentedThe documented root cause of priority inversion is using dispatch_semaphore_wait / dispatch_group_wait to fake synchronous behaviour, because the system cannot propagate priority across those primitives. The documented remedies are to remove them, or to ensure the waiting thread’s QoS is equal to or lower than the signalling thread’s. AVCaptureSession.startRunning() on the main thread is called out by name as a hang source. DocsDiagnosing performance issues earlydoc
A concrete workflow4
  1. Run the app from Xcode — it requires no recompilation.
  2. Read issues in the Issue navigator and source editor; expand the backtrace on each.
  3. Optionally click Generate in an issue’s description to have Xcode’s intelligence propose a fix.
  4. To catch these in tests, set Runtime API Checking values to On (as Failure) in the test plan’s Configurations pane.
Tips and tricks2
Interpretation traps1
  • inferredIt is a detector of patterns known to cause hangs, not a measurement that a hang occurred. A clean run is not evidence of responsiveness. Conversely, a flagged priority inversion on a path that runs once at startup may be harmless — the tool does not weight by frequency. Crew inference.
macOS / iOS applicability

Documented as an Xcode tool without platform restriction; the examples are UIKit/AVFoundation-flavoured. Crew inference: applies wherever Xcode runs your app.

Documentation clarifications for current tooling1
This tool’s evidence gaps1
  • The documentation references a WWDC session video, but the reference rendered without a resolvable title or number in the JSON the app-level crew read, so that specific pointer could not be cited.

Xcode Organizer

App & framework level

Real users, real devices, version over version. Nothing local answers this.

The question it answers

Is the version I shipped worse than the last one, for real users, on real devices?

What the sessions show and say9
  • statedData is collected from consented user devices and aggregated on Apple’s servers, requiring no app changes. WWDC21 10181@6:33exact time
  • statedThe 2021 metric set: battery, launches, hangs, memory, disk writes, scroll hitches, terminations. WWDC21 10087@0:44exact time
  • demonstratedThe regression definition: a metric is flagged when it is trending up over recent releases and the latest value exceeds the average of recent releases. The worked example is launch time averaging 1.1 s then jumping to 2.5 s. That example’s numbers; no per-claim locator was recorded. WWDC21 10087no locatorno locator
  • statedScroll hitch rate is bucketed into under 5 ms/s (smooth), 5–10 ms/s (frames dropped every couple of seconds), and over 10 ms/s (frequent drops, poor experience). See the hitch-threshold conflict panel — current documentation gives different bands. WWDC20 10076@2:23exact time
  • demonstratedDisk-write diagnostics are aggregated when writes exceed 1 GB in 24 hours, and the report shows stack-trace signatures with the percentage of total writes each accounts for, a device/OS breakdown, and a 14-day trend. Signatures can be marked resolved. No per-claim locator was recorded for this bullet. WWDC20 10076no locatorno locator
  • demonstratedThe Xcode 13 Insights field scans reports against a repository of known antipatterns and links to documentation when it matches; the demo’s SQLite example went from ~180 MB of temp-file writes and 780 ms latency to zero writes after adding an index. That demo’s numbers. WWDC21 10087@10:26exact timeWWDC21 10087@10:50exact time
  • demonstratedXcode 27 brings four changes — a redesigned Overview putting diagnostics and metrics on one screen; a new Storage metric breaking down documents, data and binary size; an animation hitches metric that covers more than scrolling, including Liquid Glass and SwiftUI animations; and Metric Goals. WWDC26 258@17:54exact timeWWDC26 258@18:26exact timeWWDC26 258@19:11exact timeWWDC26 258@19:38exact time
  • statedMetric Goals replace what were “recommendations”; the presenter notes recommendations started with launch time last year (Xcode 26) and now cover hang rate, disk writes, battery, storage and hitches, calibrated against similar apps and the app’s own historical baselines. WWDC26 258@19:42exact timeWWDC26 258@19:59exact timeWWDC26 258@20:15exact time
  • demonstratedGenerate Recommendations hands the diagnostic data to a coding agent that works through it with you, iteratively. WWDC26 258@20:32exact timeWWDC26 258@20:48exact time
A concrete workflow5
  1. Window › Organizer. It opens to the Insights overview first time, then restores your last section.
  2. Read Insights for regressions and goal recommendations before browsing individual metrics.
  3. On a metric, filter by device and by median or high value; click a version’s bar to compare it against the latest.
  4. Move from the metric spike to the diagnostic reports beneath it, and from a signature’s stack trace to the code.
  5. Mark the signature resolved after shipping the fix, then confirm on the next version’s data.
Tips and tricks5
  • Opt in to regression notifications via the Notifications button. The documented threshold is precise: a regression is “high impact” when the latest version regresses 75 percent or more against the average of the previous four App Store versions; you are notified at most once per 24 hours, only while Xcode is running, and only once per app version. Notifications cover metric regressions only — not diagnostic signatures. You must go look for those. DocsAnalyzing the performance of your ship…doc
  • Two distinct goal types: similar-app goals (apps with comparable function and technology, and for battery and disk writes normalized for usage time) and historical performance goals (your own past values). DocsAnalyzing the performance of your ship…doc
  • “Insufficient usage data available” is a wait-and-retry state, not an error. Xcode 12 lowered the usage threshold by a factor of 5. No per-claim locator was recorded. WWDC20 10076no locatorno locator
  • For automation, the App Store Connect API exposes perfPowerMetrics for apps and builds, and diagnosticSignatures / logs for builds. No per-claim locator was recorded. WWDC21 10087no locatorno locator
  • Use the Launches pane to get an ordered list of the longest-running functions at startup with the percentage of launch time each accounts for. DocsReducing your app's launch timedoc
Interpretation traps4
  • inferredPercentile confusion is the big one. The panes let you pick typical (50th) or longest (90th) values; a regression at the 90th percentile on one device family is a different engineering problem from a median shift, and the two are easy to conflate when skimming. Crew reasoning.
  • documentedLow-usage versions carry a margin of error shown alongside the value. Treating an early-release bar as a real regression is a common false positive; the margin shrinks as data accumulates. DocsAnalyzing the performance of your ship…doc
  • demonstratedField data tells you that and where, rarely why. The aggregated signature points at code, but reproduction still needs a local trace. The 2021 demo itself moves to Instruments’ File Activity to actually diagnose the disk writes. WWDC21 10087@11:13exact time
  • inferredMetric Goals are comparative, not absolute. A goal derived from similar apps is not a statement that your app is fast enough for your users. And because data comes only from consented devices, the population is not your whole user base and may skew by region, device age and OS version — so absolute rates are less trustworthy than version-over-version deltas. Crew inference.
macOS / iOS applicability

The metric set and the demos are iOS-centric; the launch-time pane is documented “for iOS apps”. The crew found no source confirming the full Organizer metric set for macOS apps and would not assert one.

Documentation clarifications for current tooling1
  • Substantially more current than WWDC20 10076 and WWDC21 10087 — it documents the Insights overview, the exact notification threshold, and both goal types. Use it wherever the 2020/2021 sessions disagree. DocsAnalyzing the performance of your ship…doc
This tool’s evidence gaps2
  • The 2020 session does not explicitly contrast field data with local traces — that framing comes from the documentation plus labelled crew inference.
  • The current full metric list was not verified against the Xcode 27 UI, and WWDC20 10057 (Power and Performance API) was not mined beyond confirming it exists.

MetricKit

App & framework level

Field metrics and diagnostics with call stacks — rewritten for 2026.

The question it answers

What are real users experiencing, with enough call-stack detail to act — and can I segment it by what my app was doing?

What the sessions show and say5
  • demonstratedThe framework was rewritten for the 2026 releases. MetricManager delivers MetricReport and DiagnosticReport values through async sequences, and the presenter states plainly that the new APIs are the future of the framework and that all the advances discussed are exclusive to them. Locator caveat: the app-level crew cited this to the session’s Introduction chapter mark. This task’s verification found that mark is a chapter start, not a transcript sentence supporting the claim, so it is shown here as a chapter-level locator rather than a timestamp. WWDC26 222Introduction chapter (0:01)named section only
  • demonstratedNew in that release: a Metal frame rate metric, memory-exception diagnostics, a termination category field on crash diagnostics, and metrics segmented by app state via the new StateReporting framework. WWDC26 222@4:07exact timeWWDC26 222@7:13exact timeWWDC26 222@10:03exact time
  • demonstratedBoth MetricReport and DiagnosticReport are Codable, so uploading is a JSONEncoder call. Reports must be consumed from app startup or data is lost, and the manager must be kept alive. WWDC26 222@4:07exact time
  • demonstratedThe legacy model it replaces: MXMetricPayload carries 24 hours of passively collected data in cumulative, averaged and bucketized forms; MXDiagnosticPayload was added in MetricKit 2.0 and maps one-to-one with the metric payload. Diagnostic types are hangs, CPU exceptions, disk-write exceptions and crashes, and the distinguishing value of diagnostics over metrics is the backtrace (MXCallStackTree). WWDC20 10081@2:11exact time
  • demonstratedThe legacy subscription pattern: conform a class to MXMetricManagerSubscriber, add it to MXMetricManager.shared, implement didReceive for both payload types, and remove on deinit. WWDC20 10081@2:11exact timeWWDC21 10181@5:45exact time
A concrete workflow5
  1. At app startup, create a MetricManager and keep it alive.
  2. Consume manager.metricReports and manager.diagnosticReports with for await.
  3. Filter metrics by metricGroup (.cpu, .memory, .display, .gpu) and switch over MetricResult cases.
  4. Encode reports to JSON and send to your server.
  5. To segment by app state, declare a StateReportingDomain, pass it to MetricManager(enabledStateReportingDomains:), and call reportTransition(to:) at each state change — metrics then arrive per state rather than blended.
Tips and tricks4
  • Signpost metrics — the honest answer. WWDC26 222 does not mention signpost metrics at all. The capability is documented: MXSignpostMetric represents a custom metric as a signpost written to a custom OS log, and the daily report carries count, duration and power/performance impact. Only events logged via MetricKit’s own utility functions (mxSignpost) capture the extra power and performance data — an ordinary os_signpost will not. The system also limits how many custom signpost metrics it saves, so instrumenting broadly will silently lose data. DocsMXSignpostMetricdoc
  • Subscribing and receiving reports is documented as safe to call in performance-sensitive code such as app launch — so there is no reason to defer setup, and deferring risks losing reports. DocsMXMetricManagerdoc
  • Your app may receive more than one metric payload per day, because some metrics come from different system sources and arrive separately. Code that assumes one payload per day will drop data. Reports also include previously undelivered daily reports, not just yesterday’s. DocsMXMetricManagerdoc
  • State reporting is the recommended way to triage a regression to a specific user flow — the lab answer describes running an experiment with small versus large batches and reading per-state metrics to decide where to invest. WWDC26 8003@22:47exact timeWWDC26 8003@23:23exact time
Interpretation traps2
  • demonstratedMetrics tell you an area is getting worse; only diagnostics tell you which code path. The 2020 session makes this distinction explicitly and it is the most useful framing for a reader deciding what to collect. WWDC20 10081@2:11exact time
  • inferredA payload covers a time range, not a session — typically many launches — so per-payload averages are not per-user or per-session figures. And diagnostics are immediate while metrics are daily; waiting a day to see whether a crash fix landed confuses the two cadences. Crew reasoning.
macOS / iOS applicability

This is the sharpest platform split in the guide. Documented: MXMetricManager receives daily metric reports on iOS 13 and later or macOS 26 and later — so metrics on the Mac are very recent. Diagnostic reports arrive immediately on iOS 15 and later and macOS 12 and later. On visionOS, MetricKit supports diagnostics for crashes, hangs, high energy use and disk writes but does not report performance metrics at all. The new async API requires iOS 27 / macOS 27, and on visionOS 27 delivers diagnostics only. DocsMetricKitdocDocsMXMetricManagerdoc

Documentation clarifications for current tooling1
This tool’s evidence gaps2
  • Whether MXSignpostMetric / mxSignpost have an equivalent in the new MetricManager API was not verified — the WWDC26 session is silent and no migration note covering custom metrics was found. A team relying on signpost metrics should treat that as an open question.
  • The deprecation status (as opposed to replacement) of the MX* types was not verified.

XCTest performance measurement

App & framework level

The only tool here that fails a build.

The question it answers

Will I find out automatically if this gets slower?

What the sessions show and say6
A concrete workflow6
  1. Create a test target; write a test… method calling measure(metrics:options:block:).
  2. Pick metrics deliberately from the verified list below.
  3. Configure the scheme and test plan before trusting any number — see the hygiene checklist.
  4. Run, open the performance overlay from the gutter icon, click Set Baseline.
  5. Set Max STDDEV to a tolerance that reflects your CI’s noise.
  6. On failure, control-click the gutter icon and choose Profile [test name] to open it in Instruments.
Tips and tricks4
  • The scheme hygiene checklist is the difference between a useful test and noise. Both sessions and the docs converge on: a separate test scheme, the Release build configuration, “Debug executable” off, code coverage off, automatic screenshot collection off, and all diagnostics off (Runtime Sanitization, Runtime API Checking, Memory Management). WWDC20 10077@8:21exact timeWWDC20 10077@8:45exact timeDocsWriting and running performance testsdoc
  • A measure block runs five times by default, which quietly breaks scroll tests — you will swipe five times over different content. Use XCTMeasureOptions to stop measuring manually, then reset app state between iterations. WWDC20 10077@7:42exact timeWWDC20 10077@8:01exact time
  • A measure block can listen for multiple distinct os_signpost intervals at once — e.g. scrollDeceleration and scrollDragging in the same swipe. With XCTOSSignpostMetric, metrics are collected only for code inside the instrumented interval, not the whole block — so the block can include setup without polluting the measurement. Scroll velocity is customisable in Xcode 12 and later. WWDC20 10077@11:24exact timeWWDC20 10077@11:12exact timeWWDC20 10077@7:29exact time
  • Change the default metric set for a whole test case class by overriding defaultMetrics. Verified current metric types (from the docs, not the videos): XCTClockMetric, XCTCPUMetric, XCTMemoryMetric, XCTStorageMetric, XCTOSSignpostMetric, XCTApplicationLaunchMetric, and XCTHitchMetric. DocsXCTMetricdoc
Interpretation traps5
  • inferredA baseline is machine-specific. A baseline set on a developer’s Mac will misfire on CI hardware; treat baselines as per-environment. Crew reasoning.
  • inferredStandard deviation is the setting people get wrong. Too tight and the suite flaps; too loose and it never catches anything. It is a noise budget, not a quality target. Crew reasoning.
  • inferredThe free launch test measures launch under test-harness conditions, which include UI-test instrumentation overhead — good for detecting deltas, not for quoting an absolute launch time to anyone. Crew reasoning.
  • inferredXCTHitchMetric exists as its own type but the 2020 session teaches hitch measurement through XCTOSSignpostMetric. A reader following the video will not discover it. The crew did not verify how the two relate — flagged, not resolved.
  • inferredBecause measurement excludes debugger and sanitizers, a performance test can pass while the debug build a developer uses daily feels slow. That is correct behaviour, but it surprises people. Crew inference.
macOS / iOS applicability

XCTest performance testing applies to both. The animation sub-metrics are UIKit-specific (scrolling and navigation transitions instrumented by UIKit), so that part is iOS/iPadOS/Mac Catalyst. Unit-test-level metrics (clock, memory, CPU, storage) are platform-neutral.

Documentation clarifications for current tooling2
This tool’s evidence gaps2
  • No session from 2021 onward covers XCTest performance measurement, so all demonstrated evidence here is 2019–2020.
  • Whether Swift Testing has a performance-measurement story was not verified, and this guide implies nothing either way.

Rendering knobs at the app level

compositingGroup · drawingGroup · Canvas
App & framework level

Two modifiers people reach for interchangeably, which do different jobs.

The question it answers

I have a lot of layers or a lot of updating views — which of these SwiftUI modifiers actually helps performance?

What the sessions show and say4
  • statedcompositingGroup() is not strictly about performance — it is about applying a visual effect to a combined result. The named surprise: applying a shadow to a ZStack applies a shadow to each individual visual element, not to the composed image; compositingGroup is how you get the latter. WWDC26 8006@56:26exact timeWWDC26 8006@56:38exact timeWWDC26 8006@57:00exact time
  • stateddrawingGroup() is the performance one. It flattens the rendered layers — not the SwiftUI views — onto a single layer. In the example recalled, performance recovered sharply even though the same number of SwiftUI views were updating, because rendering went to one drawing layer. Gestures and other SwiftUI APIs continue to work. WWDC26 8006@57:29exact timeWWDC26 8006@57:39exact timeWWDC26 8006@57:55exact time
  • statedCanvas is the third option, described as analogous to UIKit/AppKit’s draw(_:) — direct control over each drawing invocation rather than describing a view. WWDC26 8006@58:14exact time
  • demonstratedThe Organizer’s hitch metric is no longer scrolling-only. The Xcode 27 animation-hitches metric explicitly covers Liquid Glass and SwiftUI animations that the previous scroll-hitch metric missed. WWDC26 258@19:11exact time
A concrete workflow4
  1. Decide which question you are asking: a visual one (a shadow or effect should apply to the composed result) or a performance one (too many layers are being composited).
  2. For the visual question, reach for compositingGroup().
  3. For the performance question, try drawingGroup() and re-measure — it can help even when the same number of views are updating.
  4. If you want direct control over drawing invocations rather than a view description, use Canvas.
Tips and tricks1
  • If drawingGroup() helped but you expected view-count reduction, the win came from layer flattening, not from fewer SwiftUI views — do not go looking for a view-count change to confirm it. WWDC26 8006@57:39exact time
Interpretation traps1
macOS / iOS applicability

SwiftUI framework behaviour; no platform restriction stated. The Organizer animation-hitches metric is an Xcode 27 Organizer feature, which is documented in iOS terms.

Documentation clarifications for current tooling1
This tool’s evidence gaps1
  • geometryGroup() is not covered. The lab answer the crew read addresses compositingGroup, drawingGroup and Canvas only, and the panel does not address geometryGroup, so this guide makes no claim about it.

06An illustrative walkthrough: three hangs in one note-taking app

Illustrative walkthrough. Assembled from one Apple session’s own on-screen investigation. Not a real case study, and not a measurement of anything run in producing this guide.

This is an illustrative walkthrough, not a case study of any real app and not a measurement of anything anyone on this task ran. It follows the investigation Apple demonstrates in a single WWDC26 session, because that one session happens to take three different branches of the same triage flow, which is exactly the shape a teaching walkthrough needs. Every number on this page belongs to that demo, on that demo’s app and hardware, and is marked as such. Nothing here is a target or a benchmark.

Source for the whole walkthrough: WWDC26 268@3:04exact time

  1. A prototype note-taking app with drawing, images and a lasso tool feels unresponsive in three different places. Profiling starts from Product › Profile, which builds for release. The presenter chooses the Swift Concurrency template rather than plain Time Profiler, because the codebase uses Swift concurrency — and notes the Time Profiler instrument is still available inside it, so nothing is lost. An OSSignposter interval already wraps lasso selection, with subsystem “Demo App” and the points of interest category, which is what makes it appear automatically in the Points of Interest track.

    WWDC26 268@3:24exact timeWWDC26 268@3:43exact timeWWDC26 268@5:46exact timeWWDC26 268@6:04exact time
  2. The triage questionMain-thread CPU, read first

    Before reading any call tree, the session asks one question of every hang: what is the CPU doing during it? “If CPU usage is high, then the thread is busy and the work is taking too long… If the application hangs but the processor is idle, optimizing algorithms won’t be helpful. This, usually, suggest[s] that the main thread is stuck waiting for a resource to free up.” The three hangs below take the high branch, the high branch again for a different reason, and the idle branch.

    WWDC26 268@2:02exact timeWWDC26 268@2:33exact timeWWDC26 268@2:46exact time
  3. Hang 1CPU saturation, diffuseTop Functions → Run Comparisons

    Filter to the lasso-selection signpost interval; the Hangs instrument shows several hangs inside it. The call tree and flame graph show no single culprit — “these different codepaths sum together to be costly enough to cause hangs.” That diffuse shape is the cue to switch to Top Functions, which sorts by self weight: the top entry is swift_project_boxed_opaque_existential, i.e. existential unwrapping.

    Fix: replace existentials with concrete types and generics.
    Verify: Run Comparisons against the baseline, with both runs filtered to the same signpost interval. Overall execution time is down and swift_project_boxed_opaque_existential is “removed completely”; some new functions show as regressions, and “the improvements outweigh the regressions”.

    WWDC26 268@6:30exact timeWWDC26 268@10:41exact timeWWDC26 268@11:37exact timeWWDC26 268@13:30exact timeWWDC26 268@15:40exact time
  4. Hang 2Execution contentionSwift Concurrency → Swift executors

    Filter to a different hang. Main-thread CPU is “around a 100%” (that demo’s reading) — so nothing is blocked, and the idle branch does not apply. But the work is async thumbnail rendering that “inherited the Main Actor context” because it was called from SwiftUI. This is the trap the concurrency instruments exist for: being async did not move it off the main thread.

    Fix: add @concurrent to the task initializer to move it to the global executor; the compiler checks the change introduces no race conditions.
    Verify: the Swift executors instrument shows the tasks move from the Main Actor track to the global executor track — and they now render in parallel. Note this is a relocation fix, not an optimisation: making the algorithm faster would have “worked” and still left the UI competing for the main actor.

    WWDC26 268@17:32exact timeWWDC26 268@18:00exact timeWWDC26 268@18:29exact timeWWDC26 268@18:47exact timeWWDC26 268@19:02exact time
  5. Hang 3System blocking, idle CPUSystem Trace

    Navigate via the “Write to File” signpost interval. A micro hang is reported and the CPU is idle — the other branch of the triage question, and the one where a call tree tells you nothing. Switch to System Trace: pin the main thread, zoom, and read the purple syscall intervals. A write system call spans both on- and off-core time, blocking the UI. Selecting one visual segment highlights more than you clicked, because it is one continuous syscall — correct behaviour, not a UI glitch.

    Fix: route the file I/O to the background.
    Verify: “The main thread no longer shows the write system call.”

    WWDC26 268@19:30exact timeWWDC26 268@19:53exact timeWWDC26 268@22:27exact timeWWDC26 268@22:52exact timeWWDC26 268@23:10exact timeWWDC26 268@24:50exact time
  6. All three hangs are eliminated. The session’s own summary maps each symptom to its tool and closes on two pieces of advice that apply to every investigation on this page: “Always profile a release build” and “leverage os_signpost to make sure your intervals for run comparisons are reliable.”

    WWDC26 268@25:29exact timeWWDC26 268@26:12exact timeWWDC26 268@26:14exact time

Six more worked investigations, if this shape does not match yours

Each of these is walked through end-to-end on screen in the session cited. They are pointers, not summaries of findings.

  • The deep hang investigation — best for reading a trace rather than a modern fix idiom. Heaviest stack trace, Hide System Libraries, the Turtle/Unicorn digression on why sample counts cannot distinguish one slow call from many fast ones, a micro hang that is a major hang on iPad, and a thread blocked 6.64 s on mach_msg2_trap with the precise (not aggregated) backtrace. 6.64 s is that demo’s number. WWDC23 10248@9:55exact time
  • The CPU-throughput path — not a hang, but making correct code faster: a unit test exercising binary search, signposted, profiled with CPU Profiler in deferred mode, then Processor Trace to measure abstraction costs exactly, then CPU Counters preset modes for the remaining microarchitectural bottleneck. WWDC25 308@2:14exact time
  • Transient spike → persistent growth → leak — the single best memory narrative in the corpus, running from an autorelease-driven spike, through a stair-step traced to a cache keyed on Date(), to a closure-context retain cycle found for free at the end. WWDC24 10173@5:29exact time
  • Power regression, and why the desk lies — a LazyVStack fix found at the desk, then a battery drain only a commuting colleague could reproduce, captured as an on-device Performance Trace and shared as a file. WWDC25 226@2:21exact time
  • SwiftUI, both halves — part one finds a slow body (two formatters constructed per row) via Long View Body Updates and the Time Profiler handoff; part two finds an update storm (a whole-array @Observable dependency on every row) via the Cause & Effect graph. Together they are the “too slow” and “too often” halves of one tool. WWDC25 306@4:20exact time
  • Prevention rather than diagnosis — measure scroll animation performance, set a hitch-time-ratio baseline, add an image-loading feature, re-run, and the test reports the increase in hitches before the change ships. WWDC20 10077@9:13exact time
  • Organizer triage, closing the loop — spot a disk-write spike in a shipped version, read signatures by percentage of total writes, check the device/OS breakdown and 14-day trend, fix, mark resolved, confirm on later data. WWDC20 10076@2:23exact time

07Where the sources disagree

Four places where the evidence does not line up. Both readings are shown; nothing was silently resolved in favour of one side, and where a resolution is offered it is labelled as whose reasoning it is.

Hitch-ratio thresholds: two different sets of numbers, unreconciled

The 2020 material and Apple’s current documentation publish different hitch-rate bands, and Apple does not reconcile them anywhere either crew read. Both readings are shown here rather than silently picking one.

The 2020 sessions — a local measurement of one animation

Under 5 ms/s good and mostly unnoticeable · 5–10 ms/s the user notices interruptions, investigate · over 10 ms/s greatly impacting the experience, investigate immediately.

Tech Talks 10855@10:51exact timeTech Talks 10855@11:05exact timeWWDC20 10077@3:44exact time
The WWDC20 Organizer session — scroll hitch rate buckets

Under 5 ms/s smooth · 5–10 ms/s frames dropped every couple of seconds · over 10 ms/s frequent drops, poor experience.

WWDC20 10076@2:23exact time
Apple’s current documentation — the Organizer Hitches metric

“A hitch rate at or below 10 ms/s is good; at or below 25 ms/s is a warning; at or below 50 ms/s is critical; and above 50 ms/s warrants immediate attention.”

DocsUnderstanding hitches in your appdoc

The CPU crew’s reading — explicitly labelled as their inference, not Apple’s — is that these plausibly measure different things: a single local measurement of one animation versus an aggregated field metric across all animated interactions, so they are not strictly contradictory. But a reader who memorises “under 5” from the 2020 talk and then reads an Organizer number will misjudge it. Note also that the WWDC20 Organizer session itself gives the 5/10 bands for scroll hitch rate, while current documentation for the Organizer Hitches metric gives 10/25/50 — so the divergence is not purely local-versus-field; the Organizer’s own published bands have changed over time. Use the numbers attached to the tool in front of you, and say which set you are quoting.

System Trace colour conventions differ between sessions

Two sessions read different meanings off the same colours in System Trace material.

WWDC21 10258, reading a System Trace overlay for a hang

“the red line indicates system calls, the purple graph indicates virtual memory faults, and the horizontal blue bar indicates the main thread is busy doing work.”

WWDC21 10258@12:13exact time
WWDC26 268, reading the modern System Trace track

“The purple intervals you see indicate that a system call is running.”

WWDC26 268@22:52exact time
WWDC19 423, reading thread states

“gray means it’s blocked… Red means it’s runnable… Orange means it’s preempted”.

WWDC19 423@23:21exact timeWWDC19 423@23:35exact time

Read the legend in your own trace rather than trusting a colour memorised from a video. That conclusion is the CPU crew’s inference from the divergence between the cited statements, not an Apple reconciliation. The three statements also describe different tracks, which is part of why they differ.

Thread Performance Checker: session-demonstrated, or documentation-only?

The two research crews reported incompatible pictures of the evidence for this tool.

The app-level crew

“Nothing I could cite. This tool is documented but I found no session coverage I could verify.” Their whole write-up is graded documented.

The CPU crew

Found it demonstrated on screen — enabled from the scheme’s Diagnostics section, flagging a priority inversion in the Issue Navigator, with a worked example.

WWDC22 10082@3:55exact timeWWDC22 10082@5:13exact time

Resolved in favour of the CPU crew’s evidence, which this task verified. The app-level crew had not opened WWDC22 10082 — it was assigned to another crew — so their statement was scoped to their own reading, not a claim that no session exists. The tool card carries both the session demonstration and the documented workflow.

Do Apple session pages expose chapter markers?

A disagreement about method rather than about any tool, but it bears on how the locators in this guide were obtained.

The CPU crew

“Apple’s video pages expose no chapter list with readable times in the HTML I fetched. There is therefore no grade=section citation in this report.”

The memory and app-level crews

Both parsed data-start-time chapter markers and cite several by chapter name; the memory crew found chapter lists on 5 of the 14 sessions it read.

This task’s own verification pass found chapter markers on 18 of the 54 session pages, including several the CPU crew read — so chapter lists do exist on some pages. This does not invalidate any CPU-crew locator: every one of them was independently confirmed to be a real transcript sentence start time. It only means that crew reported no chapter-graded citations because their extraction did not surface chapters, not because none exist.

08Version and hardware floors

Most of the disappointment in this material comes from reaching for a tool your Xcode, OS or hardware does not have. These are the floors the sessions and documentation state.

CapabilityFloorSource
Memory compressor (iOS)iOS 7 onwardWWDC18 416@5:00exact time
File Activity instrument reworkInstruments 11WWDC19 419@27:43exact time
Xcode device conditions (network link and thermal)Xcode 11WWDC19 422@24:52exact time
XCTMetric family (memory, storage, CPU, OS Signpost)Xcode 11WWDC19 417@13:34exact time
Animation / hitch metrics via XCTOSSignpostMetricXcode 12WWDC20 10077@5:24exact time
Organizer scroll-hitch metrics and disk-write diagnosticsXcode 12WWDC20 10076@2:23exact time
HTTP Traffic instrumentInstruments 13WWDC21 10212@0:09exact time
HTTP/3 supportiOS 15 / macOS MontereyWWDC21 10212@14:48exact time
Organizer disk-write Insights antipattern matchingXcode 13WWDC21 10087@10:26exact time
Hang detection and labelling in Time Profiler & CPU ProfilerXcode 14WWDC22 10082@6:04exact time
Thread Performance CheckerXcode 14WWDC22 10082@4:43exact time
On-device hang detection (Settings › Developer)iOS 16WWDC22 10082@9:11exact time
Mergeable dynamic librariesXcode 15 and laterDocsReducing your app's launch timedoc
Processor Trace (analysis of a saved trace — any Mac)Instruments 16.3WWDC25 308@14:09exact time
Processor Trace (recording)Mac / iPad Pro with M4 or later, iPhone 16 or later; macOS 15.4 / iOS 18.4 / iPadOS 18.4WWDC25 308@14:31exact timeDocsAnalyzing CPU usage with Processor Tracedoc
CPU Counters preset modes“This year” relative to WWDC25WWDC25 308@22:35exact time
SwiftUI instrumentXcode 26, plus the latest OS release on the deviceWWDC25 306@4:06exact timeWWDC25 306@4:14exact time
SwiftUI Long View Body Updates thresholdsorange above 500 µs, red above 1000 µsDocsUnderstanding and improving SwiftUI pe…doc
Thread Performance Checker surfaced in testsXcode 26WWDC25 247@35:37exact time
Organizer launch-time recommendationsXcode 26WWDC26 258@19:42exact time
Power Profiler on-device Performance TraceiPhone with iOS 26+, iPad with iPadOS 26+DocsMeasuring your app's power use with Po… § Overviewdoc
MetricKit daily metric reportsiOS 13+ or macOS 26+DocsMXMetricManagerdoc
MetricKit diagnostic reportsiOS 15+ and macOS 12+DocsMetricKitdoc
New MetricManager async APIiOS 27 / macOS 27; visionOS 27 delivers diagnostics only, and visionOS never reports performance metricsDocsMetricKitdoc
Instruments Run ComparisonsNew at WWDC26WWDC26 268@13:30exact time
Organizer Storage metric, animation-hitches metric, Metric Goals, Generate RecommendationsXcode 27WWDC26 258@18:26exact timeWWDC26 258@19:11exact timeWWDC26 258@19:38exact timeWWDC26 258@20:32exact time
SwiftUI @State as a macro (lazy class init); ContentBuilder type-check performanceback-deployed to iOS 17 / macOS 14 and aligned releases; ContentBuilder usable at any minimum deployment target when building with Xcode 27. No per-claim locator was recorded.WWDC26 269no locatorno locator

09Session inventory

Every Apple session the three crews cited or inventoried. Titles, years and session numbers here were re-fetched from the live pages while building this guide, not copied from the reports.

Every session cited or inventoried by the three research crews, with the exact title, year and session number re-fetched and confirmed in this task. “Chapters” is the number of chapter markers found on the live page.
YearSessionExact title LengthChaptersUsed here
Tech Talks10855Explore UI animation hitches and the render loop11 min—cited
Tech Talks10856Find and fix hitches in the commit phase11 min—cited
Tech Talks10857Demystify and eliminate hitches in the render phase19 min—cited
Tech Talks111378Adapt to changing network conditions8 min—cited
WWDC16411System Trace in Depth37 min—cited
WWDC18405Measuring Performance Using Logging35 min—cited
WWDC18407Practical Approaches to Great App Performance50 min—cited
WWDC18410Creating Custom Instruments55 min—cited
WWDC18416iOS Memory Deep Dive51 min—cited
WWDC19411Getting Started with Instruments34 min—cited
WWDC19414Developing a Great Profiling Experience38 min—cited
WWDC19417Improving Battery Life and Performance39 min—cited
WWDC19419Optimizing Storage in Your App37 min—cited
WWDC19421Modeling in Custom Instruments37 min—cited
WWDC19422Designing for Adverse Network and Temperature Conditions35 min—cited
WWDC19423Optimizing App Launch43 min—cited
WWDC2010057Identify trends with the Power and Performance API14 min—inventory only
WWDC2010076Diagnose performance issues with the Xcode Organizer10 min—cited
WWDC2010077Eliminate animation hitches with XCTest13 min—cited
WWDC2010081What's new in MetricKit13 min—cited
WWDC2110022Demystify SwiftUI40 min—inventory only
WWDC2110087Diagnose Power and Performance regressions in your app15 min—cited
WWDC2110180Detect and diagnose memory issues29 min—cited
WWDC2110181Ultimate application performance survival guide23 min—cited
WWDC2110211Symbolication: Beyond the basics37 min—cited
WWDC2110212Analyze HTTP traffic in Instruments33 min—cited
WWDC2110252Make blazing fast lists and collection views22 min—inventory only
WWDC2110258Understand and eliminate hangs from your app24 min—cited
WWDC2210082Track down hangs with Xcode and on-device detection17 min—cited
WWDC2210106Profile and optimize your game's memory38 min—cited
WWDC22110350Visualize and optimize Swift concurrency24 min—cited
WWDC22110362Link fast: Improve build and launch times30 min—inventory only
WWDC22110363Improve app size and runtime performance14 min—inventory only
WWDC2310100Optimize app power and performance for spatial computing29 min11inventory only
WWDC2310160Demystify SwiftUI performance21 min4cited
WWDC2310248Analyze hangs with Instruments42 min13cited
WWDC2410173Analyze heap memory32 min14cited
WWDC25226Profile and optimize power usage in your app19 min5cited
WWDC25247What’s new in Xcode 2636 min8cited
WWDC25306Optimize SwiftUI performance with Instruments35 min5cited
WWDC25308Optimize CPU performance with Instruments32 min8cited
WWDC25312Improve memory usage and performance with Swift31 min9cited
WWDC26222Meet the new MetricKit17 min4cited
WWDC26258What’s new in Xcode 2727 min12cited
WWDC26268Profile, fix, and verify: Improve app responsiveness with Instruments26 min6cited
WWDC26269What’s new in SwiftUI28 min6cited
WWDC26321Dive into lazy stacks and scrolling with SwiftUI20 min6cited
WWDC26388Find and fix performance issues in your Metal games20 min7inventory only
WWDC268002SwiftUI for Beginners Group Lab60 min15inventory only
WWDC268003Power and Performance Group Lab60 min18cited
WWDC268006SwiftUI Group Lab60 min17cited

Verified unreachable — do not cite these

Each of these URLs returns HTTP 200 but redirects to its year index; the page carries no session title and zero transcript sentences. All six were re-confirmed in this task. They still surface in search results and in third-party write-ups, so you will meet them.

YearSessionTitle as it circulatesWhat happens
WWDC16406Optimizing App Startup TimeRedirects to the WWDC16 index. Widely cited by third parties. try the URL
WWDC16418Using Time Profiler in InstrumentsRedirects to the WWDC16 index, and is absent from Apple’s own Performance video index. try the URL
WWDC17413App Startup Time: Past, Present, and FutureRedirects to the WWDC17 index. try the URL
WWDC15708Debugging Energy IssuesRedirects to the WWDC15 index. Historical home of Energy Log teaching. try the URL
WWDC17238Writing Energy Efficient AppsRedirects to the WWDC17 index. try the URL
WWDC18228What's New in Energy DebuggingRedirects to the WWDC18 index; the index lists 227 and 230 but not 228. try the URL

10Coverage boundaries

This section is part of the guide, not a footnote. It says what is missing, what is weakly sourced, what could not be reached, and where the guidance is probably out of date.

The one citation this task changed

The verification pass found exactly one discrepancy across 386 distinct timestamped citations. The app-level crew cited WWDC26 222 at 0:01 with an exact-time grade, for the claim that MetricManager delivers reports through async sequences. 0:01 is the session’s Introduction chapter mark, not a transcript sentence supporting that claim — the substantive statement occurs later in the session. This guide therefore downgrades that citation to a named-section locator and shows it as such on the MetricKit card. It is a downgrade, never an upgrade; no locator anywhere on this page was made more precise than the research recorded.

No other discrepancy was found. All 54 cited session URLs returned the exact title, year and session number the reports claimed. Four titles differ only in typography — Apple renders “What’s new in Xcode 26 / 27” and “What’s new in SwiftUI” with a typographic apostrophe while the reports used a straight one. This page uses Apple’s exact rendering.

What this guide does not cover

  • GPU and Metal performance. WWDC26 388 (Find and fix performance issues in your Metal games) was inventoried by the memory crew but never opened. Nothing here describes Metal profiling beyond the memory-accounting facts about Metal resources on unified memory.
  • visionOS and spatial computing. WWDC23 10100 was inventoried and never opened.
  • Custom instrument authoring. WWDC18 410 and WWDC19 421 are full authoring sessions (schemas, modelers, the analysis core). They are cited here only for the signpost→schema link. A reader wanting to author an instrument needs those sessions in full.
  • Build times and linking. WWDC22 110362 and 110363 were listed, not mined.
  • UIKit list and collection-view performance beyond hitches. WWDC21 10252 is inventoried and pointed at; its list/collection specifics were not written up.
  • Anything about the .trace file format, trace sharing mechanics, or dSYM embedding. Only the weak claim that trace documents can be saved and reopened is cited.
  • macOS-specific responsiveness tooling — spindumps, sample, spindump. Apple’s hangs documentation mentions the macOS “spin” vocabulary, but no session any crew read covers macOS-specific hang tooling.
  • Numeric memory limits. The sessions say limits exist and vary by device; none publishes a number, and none uses the word “jetsam”. No megabyte figure appears anywhere in this guide.
  • Anything the three research reports labelled “unverified model knowledge”. All three crews kept a separate section of things they believed but could not confirm on an Apple page — call-tree options like Invert Call Tree and Separate by Thread, whether the Time Profiler sampling interval is configurable, @identity in _printChanges output, whether Swift Testing has a performance-measurement story, geometryGroup(), and more. None of it is in this guide. Where the omission leaves a real hole, it is named as a gap on the relevant tool card instead.

Claims that carry no exact locator

These are rendered on the page with a no locator badge rather than dropped, because the underlying material is useful. They are the weakest evidence here.

ClaimWhat is missing
The WWDC19 423 launch demo’s three findings and their figures (375 ms, 754 ms, 882 ms, 2.5 s → 300 ms)The app-level crew recorded no per-claim locator. The card cites the mark where that investigation begins, and the figures are labelled as that demo’s.
The “minimize, prioritize, optimize” framing (WWDC19 423)No locator recorded. Rendered with a no locator badge.
“Profiling overhead distorts phase durations — dyld at 6 ms CPU against 149 ms wall clock” (WWDC19 423)No locator recorded.
The Organizer regression definition and its 1.1 s → 2.5 s example (WWDC21 10087)No locator recorded.
Organizer disk-write diagnostics detail — signatures, device/OS breakdown, 14-day trend, mark-resolved (WWDC20 10076)No locator recorded for this bullet. The 1 GB / 24 h trigger itself is located, in the memory crew’s report.
“Insufficient usage data available” and the Xcode 12 usage-threshold change (WWDC20 10076)No locator recorded.
The App Store Connect API surface perfPowerMetrics / diagnosticSignatures (WWDC21 10087)No locator recorded.
SwiftUI @State-as-macro and ContentBuilder version floors (WWDC26 269)No locator recorded; the session is inventoried with a summary only.
MetricManager as the rewritten API (WWDC26 222)The app-level crew graded this time at 0:01. This task’s verification found 0:01 is the Introduction chapter mark, not a transcript sentence supporting the claim — so the guide downgrades it to a chapter-level locator. It is the one citation in the guide whose grade was changed during verification, and the change is a downgrade, never an upgrade.

Sessions that could not be reached

Six sessions are gone from Apple’s site. All six were re-confirmed unreachable during this task, and nothing on this page is sourced from any of them. Three of them — the energy trio — are the historical home of Energy Log teaching, so a search for “Instruments energy profiling” will keep leading people to material about tools that no longer ship. The full list is in the session inventory.

Where guidance may be dated, and what supersedes it

  • Time Profiler is no longer Apple’s first recommendation for CPU optimization. Any guidance reproducing pre-2025 “use Time Profiler to find hot code” advice without the aliasing caveat is now stale. This is the single most important recency correction in the CPU material. WWDC25 308@10:12exact time
  • The Energy Log / Energy Usage instrument and the Energy Diagnostics template no longer ship, with Power Profiler as the successor. Three sessions that taught them — WWDC18 228, WWDC17 238, WWDC15 708 — are unreachable but still surface in search results. The replacement direction is the memory crew’s inference; no Apple page states a deprecation.
  • WWDC16 411 (System Trace in Depth) shows Instruments 8 and is the only deep System Trace session. Its concepts are intact; its UI is a decade old. Pair it with WWDC26 268 from 20:49 onward. WWDC26 268@20:49exact time
  • WWDC18 407 shows call-tree operations (focus by context, charge to callers, call tree constraints) in a UI that has since changed. Genuinely taught, but treat as an older UI. WWDC18 407@19:43exact time
  • WWDC18 405 predates OSSignposter; the concepts transfer, the API surface has moved.
  • WWDC22 110350 predates @concurrent and current isolation rules. Use it for the instruments’ anatomy, WWDC26 268 for the fix idiom.
  • The three hitch Tech Talks predate SwiftUI’s current instrument suite, and their year (2020) is second-hand — stated by another Apple session, not printed on the Tech Talk pages. WWDC21 10181@12:50exact time
  • WWDC18 416’s memory model is current; its Xcode UI narration is not (“Xcode 10”, “Export Memgraph”, the memory-gauge chrome).
  • WWDC19 419 (File Activity) is a 2019 session with seven-year-old UI narration, and no newer session re-teaches the instrument. It remains the only substantial teaching material.
  • WWDC23 10160’s “constant views per element” rule is correct but incomplete; WWDC26 321 supersedes it with subview resolution, index-based retention, prefetching and the onAppear caveat. WWDC26 321@10:34exact time
  • WWDC20 10081’s MXMetricManager subscriber model is replaced by MetricManager, documented explicitly under June 2026. DocsMetricKit updatesdoc
  • WWDC20 10076 and WWDC21 10087’s Organizer descriptions predate the Insights overview, Metric Goals and the Xcode 27 redesign.
  • WWDC19 423’s launch advice is structurally sound but UIKit/UIScene-era, and omits mergeable libraries, the dyld Activity instrument and macOS behaviour — all of which the current documentation adds.
  • Naming drift to expect: “Export Memgraph” → “Export Memory Graph”; “MallocStackLogging” → the “Malloc Stack” checkbox; “file system activity / suggestions” → Filesystem Activity / Filesystem Suggestions; Long Representable Updates → Long Platform View Updates.
  • Signpost overhead figures are seven years old. No session after WWDC19 414 re-teaches them; treat the magnitudes as directional. WWDC19 414@6:33exact time

Evidence that is local verification, not an Apple citation

The memory crew checked which instruments still exist by running Apple’s own xctrace against a locally installed Xcode. That is primary verification of the shipping tool, but it is one machine’s evidence, not a quotation from an Apple page, and it is deliberately kept in its own class here rather than blended with the citations above. Everything in the block below comes from that check.

$ xcodebuild -version Xcode 26.3 Build version 17C529 $ xcrun xctrace list templates (relevant entries) Allocations File Activity Game Memory Leaks Network Power Profiler System Trace ... $ xcrun xctrace list instruments (relevant entries) Allocations Leaks VM Tracker Virtual Memory Trace HTTP Traffic Network Connections Filesystem Activity Filesystem Suggestions Disk Usage Disk I/O Latency Power Profiler Location Energy Model
  • Present under the same name: Allocations, Leaks, VM Tracker, Virtual Memory Trace, the Game Memory template, HTTP Traffic, Network Connections, Power Profiler, Location Energy Model.
  • Present but with renamed contents: the File Activity template survives; its instruments are now Filesystem Activity, Filesystem Suggestions, Disk Usage and Disk I/O Latency.
  • Absent, checked by exact match against the full 61-line instrument list: Energy Log, Energy Usage, Energy Diagnostics.
  • The conclusion that Power Profiler replaced them is the crew’s inference. No Apple page read by any crew states a deprecation.

Three things to hold on to

  • Every number on this page belongs to an Apple demo, on that demo’s app and hardware. No app was profiled in producing this guide. The only figures presented as targets or thresholds are the ones Apple presents that way: the 250 ms hang threshold, the 400 ms first-frame goal, the 500 µs / 1000 µs SwiftUI lane thresholds, the hitch-rate bands (which disagree with each other), the 1 GB / 24 h disk-write trigger, the 75% Organizer regression-notification threshold, the ~25% fragmentation rule of thumb, and the >20% high-CPU band in the energy gauge.
  • “Can I use the Simulator?” has three different answers in this material — yes for heap analysis, actively misleading for memory limits, and never offered at all for power. Conflating them is the easiest mistake here.
  • macOS coverage is genuinely thin. The SwiftUI instrument, XCTest, System Trace, the memory model and the CLI tools apply to both platforms. Hitch analysis, app launch, the Organizer metric set, on-device hang detection and Power Profiler are all documented or demonstrated in iOS-first terms, and MetricKit metrics reached the Mac only in macOS 26. This guide does not imply parity, and where a crew could not establish macOS applicability it says “unverified” rather than guessing.