Как я оптимизирую потребление памяти для насыщенных контентом приложений

Dec 04 2022
9 месяцев назад я начал свой путь в качестве ученика в Apple Developer Academy @Binus, и я буквально не знал многого и не имел никакого опыта в разработке iOS. Но я едва выживаю в течение последнего месяца из-за очень благоприятной учебной среды (и в то же время конкурентной ).

9 месяцев назад я начал свой путь в качестве ученика в Apple Developer Academy @Binus, и я буквально не знал многого и не имел никакого опыта в разработке iOS. Но я едва выживаю в течение последнего месяца из-за очень благоприятной учебной среды (и в то же время конкурентной ).

Короче говоря, как ученик, я получил так много знаний за последний месяц, и самый укоренившийся урок для меня — это менталитет «почему» и структура решения проблем.

У нас есть бесчисленное множество подходов к решению определенной проблемы, поскольку студенты CS мы обобщаем ее на два типа: сначала в глубину и в ширину. Но оптимальным является сочетание того и другого.

Как сказал Танос, все должно быть сбалансировано.

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

Недавно я как команда разрабатываю (и до сих пор разрабатываю ‍) приложение под названием Hayo! в макро вызове.

Хайо! приложение, которое помогает пользователям управлять своим мероприятием

Как видите, интерфейс нашего приложения очень привлекателен (спасибо нашей команде дизайнеров, которые были готовы бороться до последней минуты ✊).

С технической точки зрения количество графического контента линейно зависит от потребления памяти. Это также напоминает мне о моей первой самой приятной ошибке в академии при разработке Kelana (мое первое приложение для iOS). Kelana считается содержательным приложением из-за большого количества изображений с высоким разрешением.

«Мы не делаем ошибок, просто счастливые маленькие случайности». — Боб Росс

Выступление Келаны было плохим; на главной странице отображалось 6 изображений 4K и использовалось ≈ 350 МБ ОЗУ. Он потребляет так много оперативной памяти, потому что класс UIImageView использует необработанное загруженное изображение (4K) для пользовательского интерфейса, даже если пользователь видит его только как небольшую миниатюру . и я заметил это только несколько месяцев назад, внимательно наблюдая за тем, что происходит в памяти распределения кучи с помощью Instrument .

Оба они (Kelana и Hayo) напоминают похожие вещи, такие как горизонтальные представления коллекций, тяжелые удаленные изображения и очень богатый визуальный контент (Content-Rich).

Итак, до разработки Hayo! начал, я делаю несколько шагов назад, чтобы узнать что-то из прошлого пути ученика, и теперь я очень рад рассказать вам о процессе мышления и разобрать его.

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

В этом случае я собираюсь начать фиктивный проект, который напоминает характеристику Келаны и Хайо! изображение сверхвысокой четкости с удаленного URL-адреса.

# 1 Знайте, в чем проблема и почему это происходит

Знать, что происходит в вашем приложении, — это первое, что вам нужно сделать. Если вы не знаете, почему существует проблема, то вы будете решать ее вслепую и, возможно, закончите перебором ответов StackOverflow один за другим (*это не очень хороший подход, и я слишком ленив для этого).

В этом случае приложение должно загрузить изображение с удаленного URL-адреса. Поскольку у UIImageView не было возможности загрузки с URL-адреса, мы могли бы добавить расширение к UIImageView.

Почему у него есть вложенные фоновые и основные потоки?

Я разделяю выборку изображений в фоновом потоке и обновления пользовательского интерфейса в основном потоке (зависит от производительности)

Вуаля, мы решили нашу первую проблему и реализовали приложение, которое загружало изображение с удаленных URL-адресов. Но подождите, приложение тормозит или это только у меня так? Хром * просто ест мою оперативную память? k&#*&@ vs a&$n? С чего бы это ^*@ $@ek ?

# 2 Не чувствуйте себя подавленным, поспешным или бесполезным для решения одной проблемы, но создайте еще больше.

Вы уже чувствовали себя подавленным? Помедленнее, Ромео, мы решим это постепенно.

приложение, которое показывает 1 изображение и 1 ярлык, потребляет 100 МБ ОЗУ ; первая стоимость нашего решения

Как видите, пользователь не знал, тормозит приложение или нет. Итак, следующее, что мы могли бы сделать, это реализовать индикатор загрузки для лучшего UX.

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

Из моего опыта работы с приложениями Kelana…

Класс UIImageView использует необработанное загруженное изображение (4K) для пользовательского интерфейса, даже если пользователь видит его только как небольшую миниатюру.

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

  • Чем больше размер содержимого, тем больше потребление памяти, поэтому сложность пространства является линейной.
  • Если использование памяти резко возрастает, значит, объем используемой памяти становится грязным.
  • Если использование памяти странным образом растет, скорость отклика приложения снижается.
  • Чем чище память, тем меньше вероятность того, что приложение будет уничтожено сборщиком мусора iOS.
  • Приложение потребляет 800 МБ ОЗУ для отображения 8 фотографий

У нас есть два варианта решения этих проблем. Сначала мы запрашиваем измененное изображение на нужный нам размер. Во-вторых, мы изменяем размер изображения на устройстве и сохраняем версию с измененным размером.

Первый подход звучит как бэкенд-подход и выходит за рамки нашего опыта как iOS-инженера, поэтому второй подход более осуществим.

Индикатор загрузки для лучшего UX; дочерняя проблема, вызванная немодерируемым удаленным контентом

#3 Не торопитесь, запишите все возможные решения

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

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

Может быть, у кого-то из ваших друзей (или у вас самих) была тарабарщина о кеше, поскольку мы сталкиваемся с определенными проблемами с памятью и производительностью, верно? Итак, давайте сломаем это

Как мы знаем, согласно Apple docs, NSCache — это объект пары «ключ-значение», который хранит общий объект в контейнере. Он также сказал, что это может минимизировать объем памяти.

Давайте подумаем еще раз, кеш может минимизировать объем памяти, но если мы сохраним изображение 4K в кеше, что произойдет ?? Во-первых, это уменьшит скорость отклика, потому что сведет к минимуму вычисления.

Во-вторых, кеш также занимает столько же места, сколько и размер содержимого, и если размер кеша меньше объекта, он совершенно бесполезен. Спросите себя, стоит ли экономия времени потраченного объема памяти?

если вы не понимаете, как работает кеш, позор вам. Дж.К.

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

Таким образом, в этом случае кэширование изображений является слишком ранним и не лучшим решением. Давайте сделаем шаг назад и посмотрим под другим углом.

Ежедневно мы часто сталкиваемся с определенными случаями, когда нам следует уменьшить размер файла. В этом контексте существует так много способов уменьшить размер файла, что он называется растеризацией . когда мы сжимаем документ изображения, что на самом деле происходит за битом? TLDR посмотрите на иллюстрацию ниже

https://www.techspot.com/article/1888-how-to-3d-rendering-rasterization-ray-tracing/

Это напоминает мне время, когда я изучал веб-разработку в 4-м семестре, используя NextJS. NextJS использует растеризацию для оптимизации элементов изображения. Проще говоря, разрешение изображения сжимается ровно до размера кадра <img> на экране, что снижает использование памяти.

Теоретическая гипотеза того, что мы собирались сделать

В Phot*shop и Next.JS растеризация выполняется одним щелчком мыши, но как это сделать в Swift? К сожалению, в UIKit нет встроенной функции (пожалуйста, добавьте это для следующего обновления ). Затем мы должны углубиться в то, как происходит рендеринг изображений в UIKit.

Согласно WWDC 18 Session 219 Image and Best Practice Кайла Садлера, вот как связаны отношения между UIImage и UIImageView.

Конвейер рендеринга изображений. WWDC18

Да видео уже 4 года, но учиться никогда не поздно, правда?

Также было сказано, что мы можем активно экономить память на этапе декодирования за счет понижения частоты дискретизации (обычно это та же концепция, что и при растеризации).

Они также прикрепляют код для понижения разрешения изображений с помощью ImageIO и CoreGraphic.

Пример кода реализации понижающей дискретизации с WWDC 18

Как обычно, давайте скопируем и вставим эти образцы кода в наш проект.

О-о, приложение разбилось, и TLDR в журнале ошибок сказал, что URL-адрес не должен быть в основном потоке, и предложил запустить его с асинхронным URLSession.

#4 Не бойтесь читать официальную документацию

Документация Apple для разработчиков — единственный источник правды, к которому вы должны обратиться в первую очередь. Не бойтесь потратить секунду, просматривая его. Лично меня это тоже ошеломляет, но со временем вы к этому привыкнете.

Библия для программиста iOS

Согласно образцам документов, этим функциям требуется CGImageSource, а для создания CGImageSource требуется CFURL. CFURL может ссылаться только на локальный ресурс. *CMIIW

Общая информация: CG означает CoreGraphic, а CF означает CoreFoundation.

Это означает, что доступ к изображению должен быть локальным, но нам нужно изображение, которое хранится удаленно в облаке. Таким образом, эти образцы кода с WWDC18 могли делать то, что мы хотели .

#5 Копируйте процесс, а не исходный код

Как инженер, мы зарабатываем на жизнь копированием и вставкой кода из StackOverflow. Но что произойдет, если мы не поймем, что, как и почему работает код? Хороший код — это код, который работает стабильно и легко модифицируется. Для этого вы должны понимать, как работает каждый компонент вашего приложения и как он взаимодействует с другими.

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

Пока, насколько нам известно, коды примеров с WWDC18 не работали для удаленных образов. Однако у нас есть твердое понимание того, почему, что и как мы должны действовать. В нашем случае — уменьшить размер изображения и отобразить его на экране в зависимости от размера кадра изображения.

На первый взгляд, первое, что приходит на ум, — это выполнить итерацию для каждой области изображения (*шаг в терминах компьютерного зрения) изображения и вычислить среднее значение пикселя для каждого, а затем сопоставить его с меньшим изображением. Если вам трудно понять мою тарабарщину, посмотрите на иллюстрацию ниже

Иллюстрационное изображение с понижением разрешения пиксель за пикселем; источник: https://www.researchgate.net/figure/Pre-processing-downsampling-applied-before-using-pixel-values-of-images-We-select-the_fig3_304617539

Короче говоря, после нескольких экспериментов я нашел самый быстрый способ (и самую короткую строку кода) для понижения разрешения изображения. К счастью, нам не нужно реализовывать описанный выше алгоритм, который автоматически покрывается UIGraphicsImageRenderer . Я пишу приведенный ниже код как расширение UIImage для своих личных предпочтений по разделению задач.

ПОСМОТРИТЕ НА ОБЪЕМ ПАМЯТИ. МЫ ЭТО СДЕЛАЛИ

Итак, давайте подведем итоги, какого прогресса мы достигли

Обзор достигнутого нами прогресса

Но подождите… вы заметили что-то не так? Сначала я не заметил этого, но внимательно посмотрел между исходным изображением и версией с пониженной частотой дискретизации.

ЭТО БЫЛО РАСТЯЖЕНО

В версии с пониженной частотой дискретизации соотношение сторон изображения было растянуто из-за размера кадра контейнера (UIImageView). а правая версия сохраняет исходное соотношение сторон изображения и обрезается с помощью .scaleAspectFill. Для упрощения нам нужно сохранить соотношение сторон изображения.

# 6 Ручка и бумага — ваши лучшие друзья

После того, как мы определили проблему, мы были на полпути, поэтому давайте углубимся в то, как работает аспектЗаполнить.

Иллюстрация разницы между аспектным заполнением и аспектным соответствием

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

Потратив несколько часов на набросок геометрического подхода, я застрял на том, чтобы упростить вычисление, но кто-то уже решил его на StackOverflow. (мне плохо, что я сначала не проверил StackOverflow)

простите мой плохой почерк ; https://stackoverflow.com/questions/6565703/math-algorithm-fit-image-to-screen-retain-aspect-ratio

вот как это выглядит, когда вы реализовали его как код.

изображение сохраняет свое соотношение сторон, но использование памяти немного увеличивается, но все еще на приемлемом уровне

Заворачивать

Мы работали над многими вещами, давайте подведем итоги нашего путешествия по оптимизации нашего приложения.

такой долгий путь, чтобы улучшить мелочи и оказать ОГРОМНОЕ влияние

Разбивая проблему, потенциальное решение и воздействие; нам удается навести порядок в мыслях и сделать их менее подавляющими, не так ли?

а за счет понижения дискретизации изображения нам удается уменьшить объем памяти с 750 МБ до 37 МБ для 8 изображений и со 100 МБ до 35 МБ для одного изображения! Разве это не впечатляет?

также по наблюдениям, возможно, сложность пространства была уменьшена с O (n) до O (log n). *CMIIW

Наше приложение теперь потребляет меньше памяти, более отзывчиво, имеет меньше шансов быть убитым в фоновом режиме и потребляет меньше энергии. Другие приложения, работающие на том же устройстве, будут иметь больше памяти для работы, и в результате наши потребители будут счастливее.

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

До следующего раза, Фахри.

© 2022 Фахри Новальди, Хайо! Команда разработчиков. Все права защищены. Изображения доступны по международной лицензии Creative Commons Attribution 4.0.