Home

Getting an app from 142MB to 38MB

The App Store listing said 142MB. On cellular, Apple warns users above 200MB, and I was uncomfortably close to a threshold that would cost installs.

Getting to 38MB took about two days, and most of it was in one place I had not thought to look.

Measure the right number first

The size in Xcode’s Organizer is not the number users see. Three different numbers matter:

  • Archive size — your build output. Includes every architecture and all debug symbols. Not relevant.
  • App Store file size — after Apple’s processing and thinning for a specific device. This is the download.
  • Install size — after decompression on the device.

To see them: Xcode → Organizer → select the archive → Distribute App → App Store Connect → Export, and read App Thinning Size Report.txt. It lists the download and install size per device variant.

Guess anywhere else and you will optimise the wrong number. My archive was 142MB, and the actual download for a recent iPhone was already 96MB before I changed anything.

Where mine was

Asset catalog       58MB
Binary              21MB
Bundled JSON        11MB
Fonts                4MB
Everything else      2MB

Images — 58MB to 6MB

Almost all of it was one mistake: PNGs exported at 3x for images that were never displayed above 120 points, and several full-colour PNGs of what were effectively flat-colour icons.

Three changes:

Icons became SF Symbols or vectors. A single-colour icon has no business being a PNG at three resolutions. SF Symbols is free and scales; a PDF vector with “Preserve Vector Data” is the next best option.

Photos became HEIC. For photographic content, HEIC is roughly half the size of JPEG at equivalent quality, and every device the app supports can decode it.

I removed the 1x assets. No supported device uses them. Xcode still lets you ship them and they are pure weight.

Tip

Check for duplicate assets before anything else. Two catalogs, or an asset that exists in both the app and a package, ship twice. find . -name "*.png" | xargs md5 | sort | uniq -d -w 32 found four megabytes of exact duplicates in my case.

Bundled JSON — 11MB to 800KB

A large reference dataset shipped as pretty-printed JSON. Two changes: minify it (no whitespace), and compress it, decompressing on first launch into the app’s support directory.

If the data is truly static and queried rather than read wholesale, a prebuilt SQLite file is better again — smaller than JSON and it does not need parsing at launch.

The binary — 21MB to 12MB

Strip Swift symbols. STRIP_SWIFT_SYMBOLS = YES and DEPLOYMENT_POSTPROCESSING = YES for Release. This does not affect crash symbolication as long as you keep the dSYM.

Dead code stripping is on by default; confirm it has not been turned off.

Audit dependencies. I was linking a 4MB analytics SDK for two events, and an image library for one resize function. Both were replaced with a few dozen lines.

Static over dynamic linking helps here too, and helps launch time more.

Two things that made no difference

Bitcode. Deprecated, and it did not change the size.

Obsessing over Swift generics. Monomorphisation does duplicate code, and in an app-sized codebase it was noise next to a 58MB asset catalog.

App thinning does a lot for free

Apple builds a variant per device, so a user only downloads the architecture and asset resolution their device needs. That is why the archive is so much bigger than the download.

Two things opt you out of it accidentally:

Images loaded by filename from a folder reference rather than from an asset catalog are not thinned — every device gets every resolution. Move loose images into a catalog.

On-demand resources are worth knowing about for anything genuinely optional. Level packs, tutorial videos, offline maps — tagged and downloaded on first use rather than shipped in the initial download.

The result

Before After
Archive 142MB 41MB
Download (recent iPhone) 96MB 38MB
Install 128MB 44MB

What I would check first in another app

  1. The asset catalog. It is nearly always the answer, and nearly always 3x PNGs that should be vectors.
  2. Duplicates, with the md5 command above.
  3. Any bundled data file over a megabyte. Compress it, or turn it into a database.
  4. The dependency list, honestly. Every SDK is worth its size or it is not.

The binary is the last place to look and it is where people start, because it feels like the engineering problem. The images are where the megabytes are.