Dentii’s v3.0 release pushed the build size to 150MB. Uploads to App Store Connect took 10 minutes. Users were writing in: “the app is huge, I can’t download it on cellular.” For a small dental tracker, that size was absurd.
In two weeks I got it from 150MB to 40MB. This post walks through the techniques I used.
Baseline: how big is big?
In Xcode, run Product > Archive > Export IPA and check the size of the .ipa file. The installed size on iOS is usually a bit larger (the ipa is compressed).
For a more detailed breakdown, use Xcode’s App Thinning Report after archiving. It shows the install size per device.
Dentii v3.0:
– .ipa file: 92MB
– App Store install (iPhone 15): 145MB
– iPad install: 158MB
That was the baseline I started from.
Asset audit (biggest win)
The largest chunk of an app binary is usually resources: images, fonts, sounds, video.
First step in Xcode: analyze the contents of every asset catalog.
What I found in Dentii:
– 3x scaled images everywhere. iPhone SE uses 2x, iPhone 12+ uses 3x. The build was shipping all of them for every device. App Thinning handles this, but the asset catalog was set up badly.
– Legacy PNGs. Not needed once SF Symbols shipped, but the code still referenced custom PNG icons.
– Onboarding videos. Three onboarding clips, 10 to 15MB each. 45MB total.
– Custom illustrations. The designer had dropped high-resolution PNGs. 20+ of them.
What I did:
– Moved to SF Symbols. 40+ custom icons replaced by native symbols. 5MB saved.
– Stopped embedding onboarding videos. Stream from CloudKit. Binary shrank by 45MB.
– Converted illustrations to SVG. Vector through SF Symbols API. 80% smaller than PNG.
– Image compression. Optimized every remaining PNG with ImageOptim. 30% savings.
Total from asset work: 65MB.
Font audit
Custom fonts get embedded into the binary.
In Dentii:
– System fonts (free from Apple)
– A custom TTF for the display font (3 weights)
– A fallback monospace font
Each font is 500KB to 2MB. Bundling every weight adds up to 5 to 6MB.
What I did:
– Deleted weights I wasn’t using. I was only using two of the three anyway.
– Switched to a variable font. One file, all weights. 60% smaller.
– Removed the monospace font. Used SF with .monospaced() instead.
5MB saved.
Third-party SDK audit
I listed every SDK and checked how much each one contributed.
In Xcode: Product > Archive > Report Navigator > Archive > Size Report. The Binary section shows the size of each framework.
Dentii:
– Firebase SDK: 12MB (analytics, crashlytics, messaging, remote config)
– Intercom SDK: 8MB (chat + help center)
– Charts library: 3MB (DGCharts)
– Cryptography library: 4MB (custom encryption)
– Analytics (Mixpanel): 2MB
Third-party total: 29MB.
What I did:
– Trimmed Firebase. Kept Remote Config and Messaging, replaced Analytics with Mixpanel. 8MB saved.
– Evaluated Intercom. Chat was barely used, we moved to call support (in-app email). 8MB saved.
– Replaced the custom encryption library with CryptoKit. CryptoKit has been there since iOS 13. 4MB saved.
– Replaced the charts library with custom chart code. We only used three chart types, writing them with SwiftUI Path primitives was a two-day job. 3MB saved.
Third-party reduction: 23MB.
Swift compiler optimization
Compile settings affect size:
SWIFT_COMPILATION_MODE = wholemodule (release). Cross-file optimization, smaller binary.
SWIFT_OPTIMIZATION_LEVEL = -O (release). Balance between -Osize and raw speed.
You can use -Osize instead: smaller binary, slightly slower. For me -O is the sweet spot.
ENABLE_BITCODE = NO. Deprecated in iOS 16+, should already be off. Bitcode used to inflate binaries.
Strip debug symbols from the binary. In release builds debug symbols get baked in by default. Project settings > “Strip Debug Symbols During Copy” = YES.
DWARF with dSYM file. Debug symbols go to a separate file. Upload the dSYM to your crash reporter, don’t ship it inside the binary.
These compiler settings together got me 2 to 3MB.
Dynamic frameworks vs static
Dynamic frameworks take separate space on the file system. Converting to a static library rolls them into the binary, and overall size sometimes drops.
In my experience the difference is usually negligible. Linking time shifts a bit. Don’t flip the switch without benchmarking.
Localization strings
For an app localized into 36 languages, string files can hit 10+MB.
What I did: switched to the *.xcstrings format. Apple compiles the strings and embeds them. 50% smaller than .txt files.
1 to 2MB saved.
Asset catalog optimization
Compression settings for images in the asset catalog:
- JPEG: for photographs. 20 to 30% smaller than PNG.
- HEIC: iOS 11+. More efficient than JPEG.
- PNG-24 vs PNG-8: PNG-8 uses indexed color, much smaller if the palette is limited.
Each image in Xcode has settings for preserving vector data and color depth. Turn off what you don’t need.
App Thinning
App Thinning is Apple’s automatic size optimization. Each user only downloads the assets for their specific device.
Three components:
1. App Slicing: iPhone SE only gets 2x images. iPhone 15 gets 3x. iPad gets its own set.
2. On-Demand Resources: rarely-used assets aren’t shipped at install time, they’re downloaded on first use. I used this for Dentii’s onboarding videos.
3. Bitcode: deprecated after iOS 16. Ignore.
With App Thinning on, the user’s download is smaller than the full binary. An iPhone 15 user downloaded 45MB thanks to thinning (3x assets only, no iPad assets).
On-Demand Resources (ODR)
Assets that aren’t part of the initial install. You tag them, they download on first access.
Usage:
// Tag the asset in the catalog: "onboarding"
// Request it in code
let request = NSBundleResourceRequest(tags: ["onboarding"])
request.beginAccessingResources { error in
if error == nil {
// Asset is ready
}
}I made Dentii’s onboarding videos ODR. Not present on initial download, they fetch in 20 to 30 seconds when the user starts onboarding.
Final numbers
150MB to 40MB recovery:
| Optimization | Size saved |
|————–|————|
| SF Symbols migration | 5MB |
| Onboarding video streaming | 45MB |
| Illustration SVG convert | 10MB |
| Font optimization | 5MB |
| Firebase reduction | 8MB |
| Intercom removal | 8MB |
| CryptoKit native | 4MB |
| Custom chart code | 3MB |
| Compiler optimizations | 3MB |
| Asset compression | 10MB |
| Strings catalog migration | 2MB |
| App Thinning + ODR | 7MB |
| Total | 110MB |
Result: a 40MB binary, three days of implementation after the investigation, zero features lost.
Process: how to find the bloat
My approach next time:
- Archive, measure the .ipa size. Baseline.
- Review the Size Report in Xcode. See how much each component takes.
- Identify the top three bloat sources. Usually assets, third-party, fonts.
- Write an action plan for each. Which assets can go, which SDK can shrink.
- Prioritize by impact vs effort. SDK removal is high-impact, high-effort. Asset audit is low-effort, high-impact.
- Implement, re-measure. After each optimization: archive, compare size.
This cycle delivers real reduction in 2 to 3 weeks.
Is there an App Store penalty for large apps?
The App Store cellular download limit was 200MB (lifted to 500MB in 2024). Staying under it is critical.
But the psychological limit matters more. Users hesitate at 100MB+. Under 50MB they install without thinking.
Target 40 to 50MB for every new release.
Bottom line
App binary size is a dimension you have to actively manage. No release should regress on size. I shaved 110MB in two weeks, and held the 40 to 50MB range for three years after.
Asset audit, third-party SDK trimming, native SF Symbols, font optimization, ODR. These five techniques deliver 50%+ reduction on most apps.
When you start a new app, think about size from day one. “I’ll optimize later” is harder than being disciplined from the start.