lib_FoundationICU.so is the largest library in a Swift app on Android: 40.0 MB of the 65.7 MB in a Foundation "Hello World" built with the Swift 6.4.1 SDK (arm64, stripped like Gradle does). Almost all of it is ICU data. Following up on the proposal to filter that data, I measured which parts swift-foundation and swift-corelibs-foundation actually use, and what breaks when each part is removed.
Short version
- About 1 MB is unused by both libraries and can go for everyone.
- Another 6–10 MB per ABI is used only by a few corelibs features, so it can only go as an opt-in.
- Break iterator rules and unit data look unused (nothing calls
ubrk_open()), but removing them breaks real APIs and can crash.
What the 34 MB is
The packaged data is ICU 76, 33.98 MB, 4,855 entries:
| Data | Size | Share |
|---|---|---|
Locale and formatting data (coll, zone, curr, lang, region, rbnf, locales) |
19.66 MB | 58% |
| Converters (197) | 5.67 MB | 17% |
| Break iterator dictionaries (CJK, Thai, Khmer, Lao, Burmese) | 3.00 MB | 9% |
| Units | 2.96 MB | 9% |
| Transliteration + character names | 1.45 MB | 4% |
| Line breaking rules, StringPrep, confusables | 0.94 MB | 3% |
| Break iterator rules (word, sentence, title) | 0.15 MB | <1% |
Who uses which data
swift-corelibs-foundation links the same _FoundationICU and needs more of it than swift-foundation does. I checked both: by reading the ICU calls, and by removing each part and diffing the output of ICU-backed APIs.
| Data | swift-foundation | swift-corelibs-foundation | Verdict |
|---|---|---|---|
| Locale and formatting data | date, number, list formatting | NSString compare, DateFormatter, NumberFormatter |
keep |
| Units | Duration unit formatting, ByteCountFormatStyle |
Duration unit formatting |
keep |
| Break iterator rules | capitalized(with:), capitalization contexts, relative dates |
NSRegularExpression with .useUnicodeWordBoundaries |
keep |
| 20 converters | String.Encoding, 11 encodings |
part of availableStringEncodings |
keep |
| 177 other converters | — | availableStringEncodings (all CF encodings) |
opt-in |
| Transliteration + character names | — | applyingTransform |
opt-in |
| Break iterator dictionaries | — | word-boundary regex on CJK and Southeast Asian text | opt-in |
| Line breaking rules, StringPrep, confusables | — | — | remove |
Looks unused, but isn't
- Break iterator rules. ICU opens break iterators internally for title-casing, capitalization contexts and word-boundary regex. Without them,
capitalized(with:)gets Turkishİand DutchIJwrong, capitalization contexts are ignored, word-boundary regex finds no matches even in English, andDate.RelativeFormatStylewith a capitalization context traps on a force unwrap. - Units. Without them,
Durationprints1.0 measure-unit/duration-hourandByteCountFormatStyleprints1.234567.
Three filters, tested
I rebuilt the data with each filter and ran swift-foundation's tests on macOS (1,754), swift-corelibs-foundation's tests on Linux (1,906), and a probe that dumps the output of ICU-backed APIs (encodings, case mapping, date, number, Duration and byte-count formatting, capitalization contexts, relative dates, transforms, regex, IDNA).
| Filter | Removes | Saves | swift-foundation | corelibs |
|---|---|---|---|---|
safe |
line breaking rules, StringPrep, confusables | 0.94 MB | same output | same output |
high |
StringPrep, confusables, 177 converters, transliteration, character names | 6.69 MB | same output | 22 of 70 encodings left, applyingTransform returns nil |
all |
high + line breaking rules + break iterator dictionaries |
10.38 MB | same output | as high, plus no word segmentation for CJK and Southeast Asian scripts in regex |
Every test in both suites passes with all three filters. The corelibs tests don't cover encodings beyond the common ones, transforms or CJK word boundaries, which is why only the probe shows the breakage.
On Android
A minimal NativeActivity, Swift 6.4.1 SDK, NDK r30, arm64, all libraries stripped with llvm-strip --strip-unneeded as the Android Gradle Plugin does by default. lib_FoundationICU.so rebuilt from swift-foundation-icu (same sources as swift-6.4.1-RELEASE) with each filter, then run on an Android 16 emulator:
| Build | APK | Output |
|---|---|---|
| Foundation, SDK as shipped | 65.7 MB | reference |
| Foundation, ICU rebuilt, full data | 64.3 MB | same |
safe |
63.4 MB | same |
high |
57.7 MB | applyingTransform returns nil |
all |
54.0 MB | as high, plus CJK word-boundary regex |
Where the rest of the APK goes
What each import costs (same app, stripped, arm64):
| App imports | APK | Added |
|---|---|---|
| stdlib only | 11.2 MB | |
FoundationEssentials |
17.7 MB | +6.5 MB |
Foundation |
65.7 MB | +48.0 MB |
Foundation + FoundationNetworking |
69.2 MB | +3.5 MB |
In the 65.7 MB Foundation APK: lib_FoundationICU.so 40.0 MB (34.0 MB data, ~4 MB code), libswiftCore.so 6.9, libFoundationEssentials.so 6.2, libFoundation.so 6.1, libFoundationInternationalization.so 1.7, libc++_shared.so 1.4, Regex 1.6, the rest ~1.5.
The biggest part of the ICU data is per-language: 16.4 MB across 292 languages (median 20 KB, English 220 KB, Chinese 1.7 MB, mostly collation). Locale data for 10 common languages (en, es, de, fr, ru, ja, zh, pt, it, ko), including units, is about 3.8 MB, so per-app locale subsetting would save about 15 MB, more than any filter above.
Unstripped, the same APK is 95.6 MB: 30 MB of symbol tables and debug info, 8 MB of it in the NDK's libc++_shared.so. Gradle strips these by default, but hand-rolled packaging may not.
What I'd propose
- Apply the
safefilter inICUExport, so it is reapplied whenever the data is regenerated for a new ICU version. Small, but free. - Add an opt-in profile for apps that don't need legacy encodings, transliteration or CJK word-boundary regex: a CMake option (the Android SDK is built with CMake) and a SwiftPM trait. That's −6.7 to −10.4 MB per ABI.
The bigger lever, per-language locale data, is out of scope for these filters. The work on moving formatting data into Swift with per-locale subsetting (swift-foundation #1991 and #2330) looks like the long-term answer for that.
The Android side of this is tracked in swift-foundation-icu#95. Would a PR for (1) be welcome? And for (2), would SDK vendors ship a slim variant if it existed? I'm happy to share the scripts and probes.