KeyMob
All articles

Profiling an iPhone when you do not have a Mac

4 min readPerformanceGetting started

Many iOS teams do not work on Macs. They write the app, hand it to CI, and CI builds it on a rented Mac. That works until someone asks why is it slow on my phone, and the answer is in a tool that only runs on a machine nobody on the team has.

This article covers what is available in that situation.

Why the obvious answer does not work

Instruments is Apple’s profiler. It is excellent, and it has three requirements that are easy to overlook:

  1. It runs only on macOS.
  2. It usually needs an Xcode project, or at least a build it can attach to.
  3. Most of the useful instruments need a build with debugging enabled.

The third requirement is a problem even for teams that do have a Mac. A debug build has different optimization, different logging and sometimes different code paths. When you profile it, you measure a program that no user runs.

What the phone already measures

iOS already tracks all of this. The operating system records per-process CPU, memory footprint, GPU utilization, frame presentation, network bytes and energy attribution, because it needs them. It uses them to schedule work, to decide what to terminate under memory pressure, and to fill in the Battery screen in Settings.

These counters are exposed through the device’s own services to a computer the user has trusted. This is the same channel that lets a computer back up a phone or install a build. Reading them does not require a jailbreak, an agent on the device or an SDK in your app.

What that gives you

Connect a device, select an app, and you get a live view of:

  • CPU as a percentage of a core, per process
  • Memory footprint over time
  • GPU utilization
  • Frame rate as presented
  • Network bytes in and out
  • Energy impact

This is not everything Instruments does. There is no allocation call tree and no symbol-level time profiler. But it is enough to answer the question most performance investigations start with: which resource is the bottleneck, and when?

Often, that is the whole question

A stutter report is rarely a request for a call tree. It is a request to know whether the app is CPU-bound, GPU-bound or blocked on something. These point to very different fixes, and four lines on a chart are enough to tell them apart:

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 applies, you know which part of the code to read. A call tree is often unnecessary after that. When it is necessary, you know exactly which interaction to hand to whoever has the Mac.

The device matters more than expected

Profiling on the device you have is less useful than profiling on the devices your users have. A three-year-old handset effectively has less headroom in each frame, throttles sooner and has a battery that has lost capacity. A flagship on a desk hides all of that.

So profiling from any desktop is not only a convenience for teams without a Mac. It lets you profile on whatever hardware is available, such as a tester’s phone, a support device or an old handset in a drawer, rather than only on the devices paired with one Mac.

A practical process

  1. Get the device the complaint came from, or the oldest device you support.
  2. Let it cool down. A hot phone behaves like a different phone.
  3. Select your app.
  4. Capture a fixed interaction: the same scroll, the same distance, ten seconds.
  5. Find the drops and read the other counters at those moments.
  6. Change one thing. Capture the same interaction again.
  7. Compare.

Step six is the one people skip. One capture is a number; two captures on either side of a change are a finding.

What this does not replace

These counters have clear limits:

  • Allocation-level analysis. For a leak that does not show in the memory trend, you still need a heap tool.
  • Symbol-level CPU attribution. Knowing the app is CPU-bound does not tell you which function is responsible.
  • Network content. Byte counts are not requests. Inspecting requests needs a capture tool.

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 lies, and you can get it from a Windows or macOS computer.