DEV Community

Duchan
Duchan

Posted on

Lean mode gives up roughly half the memory saving it could have had

Running several mobile simulators on an Apple Silicon Mac is mostly a memory problem. Some of that pressure comes from the default image rather than from the app under test.

An iOS simulator uses a consumer-device image. That image includes background services a QA session may never touch. An Android emulator ships bundled Google apps that start at boot whether or not a test opens them.

Those defaults make sense for an image that behaves like a consumer device. On a development Mac running several test sessions, they are mostly cost.

tapflow's Lean mode reduces that background load. It keeps part of the image intact instead of disabling everything, and that choice costs most of the saving that was available.

The baseline: disabling everything

The comparison point is simslim's default, which disables everything and leaves keeping things to the user. Disabling everything uses 50% less memory per device.

Keeping wallpaper and widgets changes the arithmetic. They are 675 MB of what simslim saves, most of its measured total. Leaving them on gives that part up.

One label on simslim's list could not be stopped. com.apple.siri.context.service is not a launchd daemon, so the override store cannot stop it. I added an override and measured the result. The service kept running, so the label was deliberately left out of tapflow's list.

The fixed list tapflow ships

Wallpaper and widgets are part of what a tester sees. Some common app dependencies should keep working too. A test that opens the photo picker, calls StoreKit, receives a push, or uses a universal link should not fail because a memory option was enabled.

Lean mode uses a fixed list instead, selected label by label from simslim's categories. On iOS, it disables 99 launchd labels covering background work such as Siri, telemetry, Health, and photo analysis. It also covers the iMessage and FaceTime group. Wallpaper and widgets remain enabled, along with services such as CloudKit, StoreKit, push, HealthKit, the photo picker, Contacts, Calendar, and universal links.

That selection measured about 25% less memory per iOS simulator, roughly 0.5 GB, on iOS 27. Lean mode needs an iOS 18.5 or later runtime. The default is off. You enable it in tapflow.config.json:

{ "agent": { "lean": true } }
Enter fullscreen mode Exit fullscreen mode

You can also use TAPFLOW_LEAN=on or let tapflow init ask about it. An app that needs one of the disabled services should run with Lean mode off.

There is another detail behind the count. Two of the 99 labels were already off in the measured environment. That is a measurement from macOS 26.5 and 27.0 on September 25, 2026, rather than a permanent property of every runtime. The list and its memory result may need another look as Apple changes the simulator.

Android has a different timing problem

Android Lean mode disables four bundled Google apps in the emulator: Google, YouTube, YouTube Music, and Digital Wellbeing. On an API 34 emulator, that reduced the Mac's memory use by about 350 MB out of roughly 2 GB touched by the guest.

The mechanism is different from iOS. Android gives the memory back to the Mac when the emulator exits, so disabling packages inside an already running emulator cannot produce the saving immediately. tapflow records the Lean state in the emulator and applies the package changes on boot. The memory reduction starts from the second boot through tapflow.

That distinction matters when checking a result. An Android emulator that was already running when a session requested it is used as it is. It becomes lean on its next boot through tapflow. While Lean mode is enabled, the packages stay disabled even if the emulator is opened from Android Studio. Turning Lean mode off restores them on the next tapflow boot.

The home screen loses the Google search bar, and the assistant is gone. An app that sends ACTION_WEB_SEARCH finds nothing to handle it. Voice input keeps working, because other apps provide it. Photos, Messages, Gmail and Maps stay on, because apps open images, texts, mail and maps through them.

Which devices it applies to

Lean mode applies to devices that tapflow boots through its agent. A simulator or emulator that is already running keeps its current state until the next tapflow boot. tapflow boot starts a simulator without going through the agent, so it does not apply the Lean setup.

On iOS, tapflow writes the overrides before boot and removes them when it shuts the simulator down. Opening that simulator later from Xcode or Simulator.app starts it with the services running again. Android keeps its package state in the emulator, which is why its disabled apps remain disabled when opened elsewhere while Lean mode is on.

What the number does not mean

The iOS memory result can help one Mac hold more simulators before it starts swapping. I have not made that claim for Android. The Android measurement describes memory reduction. It does not establish a higher concurrent device count.

Lean mode also does not make a simulator faster. It removes background work and reduces memory pressure under the conditions above. A lower memory footprint is useful when the alternative is swapping, but it is a different claim from lower boot time or faster test execution.

What the 99-label list costs

The 99-label iOS list gives up roughly half of the saving available from turning off everything. That cost buys a simulator that still resembles the environment an app expects. The wallpaper and widgets are there, and common app services stay available. tapflow's own streaming, input, UI tree, clipboard, audio, installs, deep links, and network control were checked on a lean simulator.

Lean mode is a host-level choice with a fixed list of services it turns off. It cannot infer what an app will need. If a test depends on a service in the disabled set, the right fix is to run that session with Lean mode off.

Try it

npm install -g tapflow
tapflow init
tapflow start
Enter fullscreen mode Exit fullscreen mode

Set agent.lean to true when tapflow init asks, or add it to tapflow.config.json yourself. Use tapflow doctor ios to see whether Lean mode is enabled and how many iOS simulators are lean. tapflow doctor android reports the Android configuration. It cannot count lean emulators because their state is stored inside each emulator.

If you run several simulators on one Mac, I'd be interested to know how much of that memory is background work your sessions never open.

Top comments (1)

Collapse
 
arhancanli profile image
Arhan Canli •

Nice to see the trade-off stated as a number (25% vs 50%) instead of "lean mode saves memory". And keeping a label off the list because you measured that the override didn't stop it is exactly the kind of detail that usually gets skipped.

A few questions about the measurement:

  • Is "memory per device" resident memory or physical footprint after the simulator settles? On macOS those can differ a lot because of compressed pages.
  • How many boots per configuration? Background services start in a different order on each boot, so a single reading can be off by a few hundred MB.
  • Since wallpaper and widgets are 675 MB of the gap, would a second preset that also drops them (for headless CI where nobody sees the screen) recover most of the other half?