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
- The asset catalog. It is nearly always the answer, and nearly always 3x PNGs that should be vectors.
- Duplicates, with the
md5command above. - Any bundled data file over a megabyte. Compress it, or turn it into a database.
- 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.