Когда пользователь видит в App Store приложение весом 200+ МБ, он десять раз подумает, прежде чем нажать «Загрузить». Особенно если сидит на мобильном интернете. Apple ещё в 2017 году подняла лимит для загрузки по сотовой сети — сначала до 150 МБ, потом до 200 МБ, в iOS 13 убрала его совсем. Но психология никуда не делась: тяжёлое приложение — это плохой первый контакт.
По данным исследования Sensor Tower, каждые лишние 10 МБ снижают конверсию в установку примерно на 1-2%. Для приложения с тысячей потенциальных установок в день это уже ощутимо.
Разберём по порядку, где прячутся лишние мегабайты и как от них избавиться.
Сначала измерь, потом режь
Первый шаг — понять, что именно раздувает бинарник. Xcode умеет это показывать.
Архивируй проект (Product → Archive), затем открой Window → Organizer, выбери архив и нажми Distribute App. В процессе экспорта Xcode сгенерирует App Thinning Size Report — текстовый файл, где расписано, сколько весит приложение для каждого варианта устройства.
Второй инструмент — отчёт о размере бинарника. В Xcode открой Report Navigator (⌘+9), найди последнюю сборку и нажми на иконку документа рядом с ней. Во вкладке Build увидишь размер каждой секции: __TEXT, __DATA, __LINKEDIT и так далее. Секция __TEXT — это скомпилированный код. Если она занимает 80 МБ при приложении на 10 экранов, что-то явно не так.
Ещё один способ — команда в терминале:
size -m -l -x YourApp.app/YourApp
Покажет разбивку по сегментам и секциям Mach-O бинарника.
Ассеты — главный источник лишнего веса
По опыту, картинки и видео съедают от 40 до 70% итогового размера приложения. Здесь проще всего выиграть сразу много.
Форматы изображений. PNG — хорош для иконок и графики с прозрачностью, но для фотографий и иллюстраций это расточительство. Переходи на HEIC (Apple поддерживает его с iOS 11) или WebP (с iOS 14). Одна и та же иллюстрация в HEIC весит в 2-3 раза меньше, чем в PNG, при сопоставимом качестве.
Для статичной графики без прозрачности используй JPEG с качеством 80-85% — глаз разницу не уловит, а файл похудеет в разы.
Векторная графика вместо растра. Иконки, кнопки, декоративные элементы — всё это должно быть в SVG или PDF. В Asset Catalog нужно выставить для PDF-ассета «Preserve Vector Data» и «Single Scale». Тогда Xcode будет использовать один файл вместо трёх растровых версий (@1x, @2x, @3x).
Убери дубликаты. В проектах с историей часто оказывается, что одно и то же изображение лежит в нескольких местах под разными именами. Утилита fdupes или скрипт на Python быстро их найдёт.
Asset Catalog Compiler. Убедись, что в Build Settings включена опция ASSETCATALOG_COMPILER_OPTIMIZATION = space. По умолчанию там стоит time — оптимизация скорости компиляции, а не размера.
Видео. Если в приложении есть вводные ролики или туториалы — сжимай их через HandBrake или ffmpeg с кодеком H.265 (HEVC). Поддерживается с iPhone 7 и iOS 11. Выигрыш в размере — 40-50% по сравнению с H.264.
ffmpeg -i input.mp4 -c:v libx265 -crf 28 -c:a aac output.mp4
On-Demand Resources
Если приложение большое — игра, обучающая платформа, приложение с кучей контента — не нужно тащить всё это при первой установке.
On-Demand Resources (ODR) позволяют разбить ассеты на теги и загружать только то, что нужно прямо сейчас. Пользователь установил приложение, прошёл первый уровень — только тогда подгрузились ассеты второго.
Настройка в Xcode простая: в Asset Catalog выдели нужные ресурсы, задай тег в правой панели. В коде запрашивай их через NSBundleResourceRequest:
let request = NSBundleResourceRequest(tags: ["level_2_assets"])
request.beginAccessingResources { error in
guard error == nil else { return }
// ресурсы загружены, используй их
}
Apple хранит ODR-ресурсы на своих серверах. Бесплатно, без лишних телодвижений с вашей стороны.
ОДР особенно хорошо подходит для:
- уровней в играх
- обучающих материалов по разделам
- языковых пакетов
- сезонного контента
App Thinning
App Thinning — это механизм App Store, который автоматически доставляет пользователю только то, что нужно именно его устройству. Работает в трёх режимах.
Slicing. App Store создаёт отдельные варианты .ipa для каждой архитектуры и разрешения экрана. Пользователь iPhone 15 не получит ассеты для старых экранов. Ничего делать не нужно — достаточно хранить ресурсы в Asset Catalogs, а не просто в папке с файлами.
Bitcode. Компилируй приложение в промежуточный код LLVM, а не в финальный машинный код. App Store перекомпилирует его под конкретную архитектуру при доставке. Это позволяет Apple применять новые оптимизации компилятора без перезаливки приложения. Включается в Build Settings: ENABLE_BITCODE = YES. Правда, с Apple Silicon и Xcode 14 Apple фактически перестала его требовать — следи за актуальными рекомендациями в документации.
ODR — описан выше.
Важный момент: App Thinning работает только при распространении через App Store или TestFlight. Если тестируешь через Ad Hoc — получишь полный «жирный» IPA.
Настройки компилятора
Несколько опций в Build Settings существенно влияют на размер бинарника.
Уровень оптимизации. Для Release-конфигурации должно стоять SWIFT_OPTIMIZATION_LEVEL = -Osize (оптимизация под размер) вместо -O (оптимизация под скорость). Разница может быть 5-10% от размера бинарника.
Для Objective-C: GCC_OPTIMIZATION_LEVEL = s.
Dead Code Stripping. DEAD_CODE_STRIPPING = YES — убирает неиспользуемые функции и переменные из бинарника. По умолчанию включено для Release, но лучше проверить.
Link-Time Optimization (LTO). LLVM_LTO = YES_THIN включает межмодульную оптимизацию. Компилятор видит весь код целиком и может убрать то, что на уровне одного файла казалось нужным. Thin LTO быстрее в компиляции, чем полный LTO, и при этом даёт хороший результат.
Strip Debug Symbols. STRIP_INSTALLED_PRODUCT = YES и DEPLOYMENT_POSTPROCESSING = YES для Release. Отладочные символы не нужны в продакшн-бинарнике — они должны лежать в отдельном dSYM-файле.
Strip Swift Symbols. STRIPFLAGS = -x убирает локальные Swift-символы.
Аудит зависимостей
Сторонние библиотеки — тихий убийца размера приложения. Особенно когда проект живёт несколько лет и накопил зависимости по принципу «один раз добавили, забыли».
Алгоритм простой:
- Составь список всех зависимостей (Podfile, Package.swift, Cartfile).
- Для каждой зависимости ответь: используется ли она сейчас? Нельзя ли заменить нативным API?
- Удали неиспользуемые.
- Для оставшихся — проверь, можно ли подключить только нужную часть.
Пример: Alamofire занимает ~2 МБ. Если ты делаешь два-три сетевых запроса — напиши обёртку над URLSession на 50 строк. iOS 15 добавила async/await-методы прямо в URLSession, которые покрывают 90% нужд:
let (data, response) = try await URLSession.shared.data(from: url)
Аналогично с SDWebImage или Kingfisher для загрузки картинок — AsyncImage в SwiftUI и URLSession с кешированием решают задачу без внешних зависимостей.
Инструмент для анализа. Утилита cocoapods-size от Google показывает, сколько каждый под добавляет к финальному бинарнику. Это точнее, чем смотреть на размер исходников — некоторые библиотеки с маленьким кодом тащат за собой тяжёлые ресурсы.
Локализация
Если приложение поддерживает несколько языков — строки и ресурсы для каждого языка попадают в бандл. App Slicing вырезает ненужные языковые пакеты при доставке через App Store. Но есть нюанс: это работает только если ресурсы лежат в .lproj-директориях, а не собраны в один общий файл.
Проверь в Build Settings: STRIP_SWIFT_SYMBOLS не должен убивать строки локализации.
Также убедись, что Base Internationalization включена — она убирает дублирование storyboard-файлов для каждого языка.
Шрифты
Кастомные шрифты — ещё один источник лишнего веса. Одно семейство в нескольких начертаниях может весить 10-15 МБ.
Что делать:
- Используй только те начертания, которые реально применяются в интерфейсе.
- Рассмотри subsetting — вырезку только нужных символов из шрифта. Если приложение только для русского языка, латиница и иероглифы не нужны. Инструмент —
pyftsubset из пакета fonttools:
pyftsubset font.ttf --unicodes="U+0000-007F,U+0400-04FF" --output-file=font_subset.ttf
- iOS 13+ поддерживает системный SF Pro через
UIFont.systemFont. Если дизайн допускает — не тащи сторонний шрифт.
Как проверить результат
После каждого изменения замеряй итог. Экспортируй архив с App Thinning и смотри на App Thinning Size Report. Именно там — реальный вес, который скачает пользователь конкретного устройства, а не общий размер IPA.
Для автоматизации в CI можно парсить этот файл и падать с ошибкой, если размер превысил пороговое значение. Пишется за полчаса на bash или Python.
Чеклист того, что стоит проверить:
Сколько реально можно выиграть
По опыту, пройдя этот чеклист на типичном приложении с 2-3 годами истории, можно срезать от 20 до 50% исходного размера. Приложение на 80 МБ легко становится 45-50 МБ без потери функциональности.
Игры и приложения с богатым медиаконтентом выигрывают больше — там ассеты занимают огромную долю, и их оптимизация даёт ощутимый результат сразу.
Если ты разрабатываешь мобильное приложение с нуля или хочешь модернизировать существующее — в REEXY занимаются в том числе мобильной разработкой и могут помочь с аудитом и оптимизацией. Контакты — на r3xy.ru.
Маленькое приложение — это не только про App Store. Это про уважение к пользователю: его трафику, месту на устройстве и времени ожидания загрузки.