У современного приложения много нефункциональных требований: размер приложения, потребляемый трафик, доступность для людей с ограничениями, стабильность, скорость запуска и работы. Наше приложение запускалось очень долго, десятки секунд. Сегодня вышло обновление, в котором iOS-приложение стало запускаться в разы быстрее. Рассказываю, как так вышло и почему только сейчас.
Запускается долго
В приложении много кода, много классов и они как-то связаны. Чтобы управлять этими связями, мы использовали Dip (читай: Swinject или любой другой DI фреймворк без кодогенерации).
Работает это так: на старте приложения все зависимости запихиваются в «контейнер», а потом из него извлекается нужный класс со всеми проставленными зависимостями. Это занимает время на старте, это занимает время при открытии любого экрана.
Зато картинки красивые
Долгое время нас это даже не пугало: мы просто рисовали красивые заставки с пиццей-новинкой и не парились. Пиццы-заставки пришлось убрать, когда мы запустились в нескольких странах, потому что ассортимент везде разный. Картинка стала не такой интересной, ожидание запуска стало скучнее, но мы пропустили этот момент.
Вот наши сплеши. Красивые, но скрывающие проблему, которую мы даже не пытались решить.
После очередного релиза у нас стало тормозить перелистывание карточек в пейдж контроллере. Тайм профайлер показал, что долго извлекаются зависимости при появлении нового экрана. Почему так? Понять невозможно. Dip очень сложно дебажить из-за одинаковых абстрактных вызовов. Мы попытались разбить один общий контейнер на много маленьких, но стало только хуже. В итоге мы отключили перелистывание карточек и продолжили новогодние обновления.
Вот так выглядит Dip в профайлере. В конце списка Call stack limit reached. Сделать с этим что-то разумное невозможно.
Вылетает на 4S? Позже поправим
В это время в бэклоге валялся баг «не запускает релизную сборку на 4S», хотя дебажные запускались. Связи проблем никто не заметил, подняли минимальную версию iOS до 10 и тоже отложили изменения. На 4S мало пользователей, правда ведь?
Выпиливаем Dip: не на старте, а при компиляции
Тем не менее, стало ясно, что нужно выпиливать Dip. А на что менять? Очень своевременно нам попалась статья Dependency Injection in Swift.
Работает просто: пишем пачку функций resolve()
с разным типом (компилятор сам разберётся). Так связи перестанут просчитываться на старте, а компилятор даже сможет оптимизировать код. Это полезно и для разработки: если неправильно описал зависимости, то узнаешь об этом на запуске, а не при открытии экрана. Конечно, есть и проблемы: если функция resolve()
не поняла тип, то выдаст вот такую бесполезную ошибку с сотней кандидатов:
Мы сделали это ещё в ноябре. Изменений было ОЧЕНЬ МНОГО, а мы в этот момент запускали новый продукт «Комбо» и готовились к последним изменениям перед Новым годом. Новый код оказался в проекте перед Новым годом, но мы не выпускали его до января из-за предпраздничного кодфриза. За это три недели мы вдоволь попользовались программой в тестовом режиме, нашли проблемы и устранили их.
Так выглядит код зависимостей сейчас. Грязно, но работает. 450 мест с регистрацией и 1400 мест с извлечением.
Функционально код одинаковый, отличается только способ работы с ним. Разница в скорости видна на всех моделях. На ХS — в два раза быстрее, а на SE смотрите сами:
Так мы ускорили не только старт, но и открытие экранов. До изменений у каждого экрана уходило по 0,3–1 секунды только на зависимости.
Новый год без пиццы
В декабре нам начали писать, что приложение вылетает на старте, не запускается и после перезапуска, переустановка не помогает. Раньше для этого была только одна причина — миграции базы, но мы от этого избавились, а новых вылетов в Crashlytics не видели. Что же пошло не так?
Гипотеза: это iOS дропает приложения, если они долго запускаются. Подтверждалась версия тем, что все отзывы были со старых девайсов: 5, 5S, 6. Количество таких отзывов сильно росло, ведь пицца — это праздник, люди чаще заказывают её на Новый год. Команда волнуется, продакт волнуется, но катим новую версию только в январе.
Нам повезло, что решение было написано и оставалось только протестировать. При другом сценарии можно было потратить месяцы на поиск причин и их исправление.
Иногда сложно донести важность технической задачи до бизнеса: нет метрик, не ясна важность, не спрогнозировали опасность. Бизнес может решать проблему разными способами: так мы рисовали красивые картинки, а не ускоряли приложение. Но перформанс важен для каждого пользователя. Он сильно влияет на:
- Ожидание. Если приложение долго запускается, то сколько они будут везти пиццу?
- Удовольствие от использования. Почему тупит на каждом экране?
- И даже на стабильность работы. «Приложение на запускается!!! Разработчики, вы там тестируете вообще?»(с).
И обходите стороной Dip, Swinject и другие фреймворки, которые работают с контейнерами в реальном времени, если у вас крупный проект и много зависимостей.
Чтобы не пропустить следующую статью, подписывайтесь на канал Dodo Pizza Mobile.
Автор: akaDuality