Profiling an iPhone when you do not have a Mac
4 min readPerformanceGetting started
Half the iOS teams I have worked with are not on Macs. They write the app, hand it to CI, and CI builds it on a rented Mac somewhere. That works right up to the moment somebody asks why is it slow on my phone, and the answer turns out to live in a tool that only runs on a machine nobody in the room owns.
This article is about what is actually available in that situation.
Why the obvious answer does not work
Instruments is the sanctioned profiler. It is excellent, and it has three requirements that are easy to overlook:
- It runs on macOS only.
- It generally wants an Xcode project, or at least a build it can attach to.
- For most of the interesting instruments, it wants a build with debugging enabled.
The third one is the quiet problem even for teams that do have a Mac. A debug build has different optimisation, different logging, sometimes different code paths. When you profile it, you are measuring a program that no user is running.
What the phone knows on its own
Here is the thing the tooling conversation usually skips: iOS is already measuring all of this. The operating system tracks per-process CPU, memory footprint, GPU utilisation, frame presentation, network bytes and energy attribution, because it has to — that is how it schedules, how it decides what to kill under memory pressure, and how it fills in the Battery screen in Settings.
Those counters are exposed over the device’s own services to a computer the user has trusted. That is the same channel that lets a desktop back up a phone or install a build on it. Reading them does not require a jailbreak, an agent on the device, or an SDK in your app.
What that gives you
Attach to a device, scope to an app, and you get a live read on:
- CPU as a percentage of a core, per process
- Memory footprint over time
- GPU utilisation
- Frame rate as presented
- Network bytes in and out
- Energy impact
That is not everything Instruments does. There is no allocation call tree, no time profiler down to the symbol. What it is is enough to answer the question most performance investigations actually start with: which resource is the bottleneck, and when?
Which is usually the whole question
A stutter report is not really a request for a call tree. It is a request to know whether you are CPU-bound, GPU-bound, or blocked on something. Those three point at completely different fixes, and you can tell them apart from four lines on a chart:
| At the stutter | Bound by |
|---|---|
| CPU spikes, GPU flat | Work on the main thread |
| GPU spikes, CPU flat | Render cost — overdraw, blurs, offscreen passes |
| Memory jumps | A large allocation, usually image decoding |
| Nothing moves | Blocked elsewhere, or thermal throttling |
Once you know which row you are in, you know which part of the code to read. Getting a call tree afterwards is often unnecessary, and where it is necessary you now know exactly which interaction to hand to whoever has the Mac.
The part nobody expects to matter
Profiling on the device you have is worth less than profiling on the device your users have. A three-year-old handset has a smaller frame budget in practice, throttles sooner, and has a battery that has lost capacity. A flagship on a desk hides all of that.
The practical consequence is that being able to profile from any desktop is not just a convenience for Mac-less teams. It means you can profile on whatever hardware is in the room — a tester’s phone, a support device, the old handset in the drawer — instead of only on the two devices that happen to be paired to the one Mac.
A workable process
- Get the device the complaint came from, or the oldest device you support.
- Let it cool. A hot phone is a different phone.
- Scope to your app.
- Capture a fixed interaction — the same scroll, the same distance, ten seconds.
- Find the dips and read the other counters at that moment.
- Change one thing. Capture the same interaction again.
- Compare.
Step six is the one people skip. A capture on its own is a number; two captures either side of a change are a finding.
What this does not replace
Be honest about the gaps:
- Allocation-level analysis. For a leak you cannot see in the memory trend, you still want a heap tool.
- Symbol-level CPU attribution. Knowing you are CPU-bound is not the same as knowing which function.
- Network content. Byte counts are not requests. That is a capture tool’s job.
What device-level counters do is turn “it feels slow” into “it is GPU-bound during the transition on this handset”. That is where most of the value in a performance investigation is, and it is available from whatever desktop you are already sitting at.
