Разработка
Как уменьшить потребление памяти при использовании UIImage
Одна из потенциальных сложностей, с которой можно столкнуться при такой задаче — Out of memory exception. Почему может возникать такое исключение?
Статья канала iOS Dev, в котором iOS-разработчик, который делится фишками, своим опытом и опытом других.
Почему ваша картинка размером в 2 мегабайта может отъедать 80 Мб памяти? Или как пофиксить одну из причин Out of memory exception? Давайте разберёмся.
На собеседованиях часто просят отобразить ленту из картинок, взятых откуда-нибудь из стороннего ресурса, Google или, например, условного Unsplash или откуда-нибудь ещё. Скорее всего, и у инженеров на боевых проектах рано или поздно может возникнуть подобная задача.
Одна из потенциальных сложностей, с которой можно столкнуться при такой задаче — Out of memory exception. Почему может возникать такое исключение?
Причины возникновения Out of memory exception
Использование памяти нашим приложением резко возрастает, когда мы начинаем показывать HD-изображения на экране (даже одно уже может внести существенный вклад в увеличения использования памяти).
Главное, что нужно запомнить:
использование памяти ≠ размеру файла
И вот почему.
Использование памяти связано с размерами изображения, а не с размером файла (Session 416, WWDC 2018).
Всё дело в том, что для отображения изображения на экране, iOS сначала необходимо декодировать и распаковать изображение. Обычно 1 пиксель декодированного изображения занимает 4 байта памяти — 1 байт для красного, 1 байт для зеленого, 1 байт для синего и 1 байт для альфа-канала (да-да, тот самый SRGB).
Например:
(3648 * 5472) * 4 bytes ≈ 80 MB
И именно это значение и будет отображаться, когда мы проверим, что же происходит в приложении.
Что со всем этим делать?
Разработчики в этой статье пробовали менять масштаб и перерисовать изображение, но это не помогло. Это связано с тем, что операции для изменения размера для UIImage дороги. В процессе изменения размера iOS по-прежнему будет декодировать и распаковывать исходное изображение, вызывая нежелательный скачок памяти.
Одно из решений, которым делятся инженеры Apple — использовать даунсэмплинг.
Даунсэмплинг
Мы можем использовать Image I/O для изменения размера изображения перед его отображением на экране. По факту, для нас это выльется только в стоимости ресайзинга исходной картинки:
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat = UIScreen.main.scale) -> UIImage? { // Create an CGImageSource that represent an image let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions) else { return nil } // Calculate the desired dimension let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale // Perform downsampling let downsampleOptions = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels ] as CFDictionary guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil } // Return the downsampled image as UIImage return UIImage(cgImage: downsampledImage) }
Флаги, которые можно использовать
- kCGImageSourceShouldCache — когда для этого флага установлено значение false, мы сообщаем основному фреймворку, что нам нужно только создать ссылку на источник изображения и не хотим декодировать изображение сразу при создании объекта CGImageSource. В ситуации, когда у вас нет доступа к пути к источнику изображения, вы можете создать объект CGImageSource, используя инициализатор CGImageSourceCreateWithData().
- kCGImageSourceShouldCacheImmediately — этот флаг указывает, что декодировать изображение нужно именно в тот момент, когда мы запускаем процесс понижения дискретизации.
- kCGImageSourceCreateThumbnailWithTransform — установка этого флага в значение true очень важно для сохранения исходной ориентации.
Подводные камни (а как же без них)
Имейте в виду, что даунсэмплинг — это процесс, который потребляет ресурсы процессора. Таким образом, по-прежнему предпочтительнее использовать правильно масштабированный источник изображения, а не понижать дискретизацию HD-изображения. Другими словами, вы должны использовать даунсэмплинг только тогда, когда вам нужно отобразить изображение, размер которого намного превышает требуемый размер на экране.
Важные моменты
- Помните, что память — это конечный и общий ресурс.
- Используйте встроенный в Xcode мониторинг.
- Позвольте iOS выбрать ваши форматы изображений, когда это возможно.
- Используйте ImageIO для понижения разрешения изображений (помните про подводные камни).
- Выгружайте крупные ресурсы, которые находятся за пределами экрана.
- Не игнорируйте графики памяти (memory graphs), чтобы лучше понять, что происходит.
Что почитать ещё?
- Управление памятью в Swift
- Память в Swift (куча, стек, ARC)
- Deep Dive into iOS Memory
- Reducing Memory Footprint When Using UIImage
- Session 416, WWDC 2018
- Image I/O
- CGImageSource
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2024.52
-
Обучение4 недели назад
Просто делать проекты, чтобы учиться, может быть плохим советом
-
Разработка4 недели назад
Как мы работаем с производительностью Threads для iOS
-
Видео и подкасты для разработчиков4 недели назад
Задачи с собеседования: LRU Cache — leetcode