Pull to refresh

Неприятные аспекты работы с Images.xcassets: размер и память

Development for iOS *Objective C *Xcode *
Добрый день, уважаемые хаброжители!

Images.xcassets даёт много приятных вещей, особенно мне нравятся Slicing и возможность добавить отдельную картинку для каждого типа устройства. Поэтому, начиная новый проект, я, даже не задумываясь, создаю Images.xcassets, добавляю туда иконки и splash, а потом и все новые ресурсы кидаю туда же. Сегодня я узнал пару неприятных вещей о xcassets, это заставило меня отказаться от него и потратить пару часов на переделку проекта. Не самое приятное и благодарное занятие. Если вы хотите учиться на чужих ошибках, прошу под кат.

Небольшая предыстория
С группой энтузиастов взялся делать сказки для детей на iPad. Заранее понимаю что на этом не разбогатеть, нашей целью является хорошее приложение, которое работает на всех девайсах с iOS 8. Так же, хотелось избежать до боли знакомой ситуации — вы скачали приложение, запускаете и узнаете, что для работы ему надо скачать еще >100МБ. Так что в приложение сразу хотелось сложить хотя бы одну книжку, чтобы после установки приложением можно было пользоваться. Книжка — это иллюстрации в формате jpeg, и без сжатия самая маленькая книжка весит 82Mb, остальные 100+Mb. А значит, если мы хотим оставить возможность скачивать его по 3g, на приложение у нас остается 18Mb. Это звучит невыполнимо, так как это детское приложение, и в нем много картинок, и большинство из них с орнаментом и узором, так что их ни ужать, ни порезать. Вот при таких изначальных данных я создал xcode проект.

Первый бой за память
Когда я начинал проект, у меня из тестовых девайсов был только iPad Air. На нём все летает, чего уже там греха таить — плохой выбор для тестирования. Поэтому, ближе к завершению проекта я приобрел iPad mini 1 gen — девайс без ретины и с процессором A5, но имеет iOS 8, а потом еще и iOS 9. При отладке приложения я время от времени получал memory warning. Не порядок, так быть не должно. Открываю «Debug» вкладку и вижу, что приложение откушало 100 Mb. И это при том, что я ничего особенного не делал, просто открыл приложение и имел в navigation stack всего 3 контроллера. Открываю профилировщик и вижу, что 70Mb занимают картинки. 100 Mb на iPad mini — явно перебор. Так что пришлось пойти и добавить большое число картинок со 1x scale (до этого их не добавлял, зная что для неретины iOS сама мне подготовит их), это, по идее, не большой удар по размеру приложения, но зато резко сократится обьем используемой памяти. В итоге я получил 47Mb. На мой взгляд, по-прежнему неоправданно, но зато перестали прилетать memory warnings.

Бой за размер
И вот вчера я закончил разработку приложения. Конечно, остались какие то TODOs, да и ребята его потестируют и, наверно, что-то найдут, но первая фаза закончена. Я на радостях выкатил билд остальным членам команды, заодно посетовал, что он весит 142Мб, а это значит, что книжку, которая вшита в приложение, нужно будет уменьшить в 2 раза по обьему, очень неприятный и заметный для глаза downgrade качества иллюстраций. Именно поэтому сегодня я решил заняться размером приложения и уменьшить его всеми правдами и неправдами, дабы не так сильно страдало качество.

Шаг 1 — посмотреть, что же занимает так много места
Беру build.ipa, переименовываю его в build.zip, разархивирую, и смотрю содержание build.app. Нахожу файл Assets.car, который весит 53Mb. В принципе, размер приложения объясним — 80 + 53 + по мелочам. Понимаю, что просто так его не раскрыть, решил посмотреть сам Images.xcassets в проекте. Она весит всего 38Mb. Это что же получается-то? Туда сложили что-то на 16Mb, а мне об этом сказать, видимо, забыли.

Шаг 2 — что же туда добавили-то?
Беглый поиск по просторам сети выдает мне cartool, для просмотра Assets.car. Делаю все по инструкции — собираю проект, беру собранный cartool и запускаю на моём Assets.car. Смотрю что у меня получилось в результате — 130 файлов общим размером 41.5Mb и, если 3.5Mb я могу списать на pngcrush, то куда ушли 11.5Mb?
Повторяю беглый поиск по сети. Нахожу подобные проблемы, к примеру, тут. И что я плачу-то из-за каких то 29%, там вообще в 6 раз вырос размер. Но это не мой случай — у меня все картинки png, так как почти все изображения имеют участки прозрачного фона. Больше я не нашел никакой надежды на исправление этой ситуации. Проверяю еще раз документацию Apple — они сделали бинарный файл, чтобы ускорить скачивание приложения. Как по мне, скорость скачивания напрямую зависит от размера. Поэтому я принял решение убрать все картинки из Images.xcassets и добавить их в проект по старинке. Мне пришлось в нескольких местах делать slicing в коде, используя
- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets

Такими махинациями я смог сократить размер приолжения с 142Mb до 131Mb. Я однозначно продолжу оптимизацию ресурсов в приложении, но 10Mb за отказ от Images.xcassets уже неплохо.

Шаг 3 — что же стало с памятью?
Запустил на iPad mini — 23Mb против 47Mb. На iPad Air 66Mb против 100Mb.

Заключение
Может показаться, что разница между 142Mb и 131Mb невелика. Но это очень значительный показатель без ухудшения качества картинок и прочих трюков. Это значительный шаг к порогу в 100Mb и доступности приложения на 3G сетях. Да и сокращение памяти при использовании приложения — это очень приятный бонус.
Я не заметил особой разницы при старте приложения. Даже наоборот — я и так вставил задержку, чтобы пользователь подольше полюбовался на наш логотип. И впоследствии проблем со скоростью работы не наблюдалось, так как приложение имеет много неспешных анимаций и заметь проседание в производительности невозможно.

P.S.
Я знаю, что с iOS 9 все должно поменяться и apple будет предоставлять контент только нужный для данного устройства. Но, даже когда я удалил все 1x изображения — размер билда стал 135. При этом я пожертвовал памятью на неретиновых девайсах и качеством изображений, что заметно невооруженным глазом на некоторых элементах интерфейса.

P.S.S.
Раз тут речь зашла о памяти, то хотел бы поделится еще одним интересным фактом. Я всегда создаю изображения с
+ (UIImage *)imageNamed:(NSString *)name;

Быстрый и удобный способ, имеющий побочное действие. Когда пользователь отправляет приложение в background, система вполне может выкинуть картинку из памяти (а на iPad mini она это делает всегда), она ведь точно знает, что это изображение взято из Bundle, и оно наверняка еще на месте. Когда приложение вернется в foreground, система подгрузит их, как только они понадобятся. Звучит вполне логично и целесообразно. Однако, может вызывать лаги при открытии приложения из background, которых не было даже при холодном старте приложения. В моем случае это была карусель, и она лагала только когда приложение поднимали из background.

Всем хороших выходных и поменьше вот таких неприятных сюрпризов.
Tags: xcodeiOSxcassets
Hubs: Development for iOS Objective C Xcode
Total votes 14: ↑11 and ↓3 +8
Comments 19
Comments Comments 19

Popular right now