FPS and jank
“Sixty FPS average” and “smooth” are different claims. A capture that averages 58 can contain a dropped frame every second, and users feel every one of them.
What the number is
Section titled “What the number is”iOS presents frames on a fixed cadence — 16.7 ms on a 60 Hz screen, 8.3 ms on a 120 Hz ProMotion display. A frame is dropped when the work for it is not finished when the cadence comes round. The counter reports frames actually presented per second.
So on a 120 Hz device, a steady 60 is not a failure; it is ProMotion sitting at half rate, which is normal for static content. What is a failure is 120 falling to 40 for a moment while the user is dragging.
Read the dips, not the mean
Section titled “Read the dips, not the mean”In a KeyMob capture, do this in order:
- Find the dips. Anywhere the line drops sharply and comes back.
- Note when they happen. During launch? On the first scroll? Every time an image loads?
- Read the other counters at that same moment. This is the diagnosis.
| At the dip | Likely cause |
|---|---|
| CPU spikes | Work on the main thread — layout, JSON parsing, image decoding, a synchronous file read |
| GPU spikes, CPU flat | Render-bound — overdraw, blurs, shadows, offscreen passes, too many layers |
| Memory jumps | A large allocation, often decoding a full-resolution image to display a thumbnail |
| Network active | Waiting on a request on the wrong thread |
| Nothing moves | Something blocking outside your process, or thermal throttling |
Common causes worth checking first
Section titled “Common causes worth checking first”- Image decoding on the main thread. Loading a 4000-pixel photo into a 200-pixel cell decodes the whole thing. Downsample before display.
- Cell reuse that is not reuse. A list rebuilding views instead of recycling them shows as CPU that scales with scroll speed.
- Blur and shadow. Both are cheap on a desktop GPU and expensive on a phone. Shadows on a moving view are the classic case: give them an explicit path.
- Synchronous work in a scroll callback. Anything touching disk or the network during a drag will be visible.
- Autolayout in deep hierarchies. Constraint solving grows faster than the view count.
Testing honestly
Section titled “Testing honestly”- Use the oldest device you support. A frame budget is a fixed 16.7 ms; a fast chip hides sloppy work that an old one will not.
- Let the device cool. A throttled phone produces a stutter that no code change will fix.
- Reproduce the same interaction each run. Same list, same distance, same speed.
- Capture over the cable. Wi-Fi sampling introduces a jitter of its own, which is indistinguishable from the jitter you are hunting.
Beyond the frame rate
Section titled “Beyond the frame rate”Some stutter never shows in FPS: a tap that takes 300 ms to respond, an animation that starts late. Watch the CPU trace and the live log around the interaction; a long gap in log output during a drag is often the clearest evidence you will get.
