Site icon AppTractor

Я уменьшил iOS-приложение с 200 до 8 МБ: побочные эффекты, которых я не ожидал

Существует особый вид счастья, понятный только iOS-разработчикам. Он возникает в тот момент, когда вы открываете Xcode Organizer, смотрите на свою последнюю сборку и замечаете, как неожиданно уменьшается её размер. В моём случае он уменьшился с 200 МБ до 8 МБ. Я не моргал. Я даже не дышал. Я просто смотрел на эту крошечную цифру, как на какой-то божественный дар.

Восемь мегабайт. Целое работающее iOS-приложение. Реальное приложение, которым пользуются тысячи людей.

Я почувствовал, что только что победил законы физики.

На несколько секунд я представил, как пишу какой-нибудь драматичный заголовок на Medium о том, как я совершил невозможное. Я представил комментарии. Я представил, как облачное хранилище лично благодарит меня.

Но к концу недели это счастье превратилось во что-то другое.

Не в страх. Не в грусть. Что-то хуже обоих.

В сожаление.

Потому что уменьшить размер приложения с 200 до 8 МБ — легко.

Жить с последствиями этого — вот это нелегко.

Это история о том, как я слишком много оптимизировал, слишком поздно это понял и случайно создал один из самых запутанных кошмаров отладки в своей карьере.

Как всё начиналось

Наше приложение постепенно росло на протяжении многих лет. Новые экраны, новые изображения, новые фреймворки, новый технический долг. Обычная история. Мы трогали всё, кроме папки с ресурсами — эта папка стала священной.

Но наш технический директор внезапно захотел легковесное приложение. Не просто немного легче. Он хотел, чтобы размер «ощущался небольшим». Что-то, что загружалось бы мгновенно даже в медленной сети в сельской местности.

Он небрежно сказал:

«Постарайся, чтобы оно весило меньше 20 МБ».

Я воспринял это как личный вызов.

В итоге мне не удалось уменьшить размер до 20 МБ.

Я довел его до 8.

Позже я узнал, что это достижение досталось мне дорогой ценой.

Шаг 1: Удаление тяжелых ресурсов

Первые несколько гигабайт (ладно, мегабайт) пришли из очевидных мест: огромные PNG-файлы, устаревшие сплеш-скрины, забытые иконки, неиспользуемые изображения 2016 года, случайные тестовые видеоролики, которые кто-то когда-то добавил и так и не удалил. Я всё почистил. Я всё разрезал. Я всё сжал.

Размер приложения уменьшился с 200 МБ до 140 МБ. Хорошо, но не очень впечатляет.

Шаг 2: Сжатие оставшихся ресурсов

Затем я углубился в проблему.

Я начал использовать инструменты для повторного сжатия изображений, преобразования в WebP для анимации, замены на векторные изображения. Всё работало. Визуальной разницы не было. Ещё одно резкое падение.

140 МБ → 60 МБ.

Хороший скачок, но всё ещё недостаточно.

Шаг 3: Магия компилятора Swift

Затем я нашёл комбинацию, которая казалась чит-кодом:

С каждой сборкой размер продолжал уменьшаться.

Пока однажды волшебным вечером это число не превратилось в:

8 МБ

Я сделал это. Я создал микроскопическое iOS-приложение.

Я отправил его в TestFlight с гордой улыбкой.

На следующее утро начали поступать сообщения об ошибках.

Побочный эффект №1: приложение стало быстрым… а затем странно медленным

Когда тестировщики скачали сборку, первая реакция была положительной.

«Скачивается очень быстро».

«Установка мгновенная».

«Время запуска отличное».

Идеально.

Но затем произошло нечто странное. После нескольких минут использования приложение внезапно замедлялось.

Не просто немного. Не время от времени. Оно едва ползало. Прокрутка тормозила. Анимация прыгала, как в старых стоп-моушн фильмах. Некоторые экраны на секунду-две полностью переставали реагировать.

Ничего из этого не имело смысла. Я не трогал никакую бизнес-логику.

Тогда я понял правду:

Моя агрессивная компрессия заставляла iOS распаковывать ресурсы на лету — и это было недешево.

Небольшое приложение. Тяжелая среда выполнения.

Я перенес затраты с установки на выполнение.

И затраты на выполнение были огромными.

Побочный эффект №2: логи сбоев превратились в бессмыслицу

Второй сюрприз ждал меня, когда начали поступать отчеты о сбоях.

Большинство из них выглядели так:

Thread 0 Crashed:
0   ???      0x0000000100000000
1   ???      0x0000000100000050
2   ???      0x0000000100000100

Все символы были удалены. Все ссылки стали бессмысленными. Каждый трассировочный стек указывал на загадочные адреса памяти.

Обычно удаление символов допустимо — до тех пор, пока вы не переборщите.

Я удалил не только отладочные символы, но и определенные карты символов, которые программа для составления отчетов о сбоях использует для восстановления читаемых трассировочных стеков.

Логи сбоев стали непонятными.

Я не мог воспроизвести проблемы. Я не мог отследить проблемы. Я даже не мог предположить причины проблем.

Каждый сбой выглядел одинаково: просто куча пустых строк и нулей.

Отладка превратилась в слепую игру «горячо-холодно».

Побочный эффект №3: локальная отладка стала работать некорректно

Что еще хуже, локальная отладка начала вести себя странно.

Точки останова не срабатывали в половине случаев. Адреса памяти выглядели некорректно. LLDB отказывался проверять переменные, возвращая сообщения следующего вида:

error: unable to read memory

Почему? Потому что я удалил информацию о символах, от которой зависит отладчик. Xcode не мог сопоставить память с известными именами переменных.

В какой-то момент я пять раз подряд прошёлся по одной и той же строке кода, но программа так и не сдвинулась с места. Было ощущение, будто отлаживаешь призрака.

Побочный эффект №4: загадочные анимации и отсутствующие элементы пользовательского интерфейса

Это было самое странное.

Некоторые элементы пользовательского интерфейса иногда не отображались. Некоторые анимации останавливались на полпути. Некоторые экраны загружались без текста. Некоторые кнопки появлялись без иконок.

Всё было непоследовательно.

Сначала я подумал, что это ошибка параллельного выполнения.

Или проблема с компоновкой.

Или проблема с таймингом.

Но нет.

Настоящая проблема?

Некоторые агрессивно сжатые ресурсы не смогли декодироваться перед рендерингом.

Когда это происходило, iOS молча загружала временные плейсхолдеры.

Прекрасная оптимизации, ужасная визуальная составляющая.

Побочный эффект №5: App Store пометил приложение как «подозрительное»

Последний сюрприз. Apple пометил сборку как «неожиданно малый размер бинарного файла для приложения с заявленными возможностями».

Перевод: «Ваше приложение слишком маленькое. Докажите, что оно легитимно.»

Мне пришлось объяснять каждую оптимизацию, обосновывать отсутствие символов и демонстрировать, что мы не используем скрытые API или трюки с динамической загрузкой кода.

Вот о чём вас никто не предупреждает.

Слишком сильно уменьшите размер приложения, и Apple подумает, что вы делаете что-то опасное.

И, честно говоря, я не мог их винить. Если бы я увидел 8-мегабайтное приложение, работающее так же, как наше, я бы тоже заподозрил неладное.

Что я узнал

1. Оптимизация приложения нелинейна

Немного уменьшить размер — хорошо. Слишком сильно уменьшить размер — физика возьмёт верх.

2. Маленькие сборки не всегда хороши

Память, время декодирования, стоимость выполнения — всё это имеет значение.

3. Удаление символов — палка о двух концах

Вы получаете больше места. Вы теряете возможность отслеживать сбои.

4. Способность отладки важнее веса бинарного файла

5. Рецензенты App Store замечают всё

Даже подозрительно маленький бинарный файл.

Стоило ли это того?

Честно говоря? Нет.

Позже мы увеличили размер приложения примерно до 35 МБ — это здоровый, сбалансированный вес. Отладка снова заработала. Производительность стабилизировалась. Логи сбоев стали понятными. Apple перестала выдавать нам странные предупреждения.

8 МБ — это был крутой момент. Забавная история. Техническое достижение.

Но это также стало напоминанием.

То, что что-то возможно, не означает, что это нужно делать.

Источник

Exit mobile version