Site icon AppTractor

Как уменьшить потребление памяти при использовании UIImage

Статья канала 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)
}

Флаги, которые можно использовать

  1. kCGImageSourceShouldCache — когда для этого флага установлено значение false, мы сообщаем основному фреймворку, что нам нужно только создать ссылку на источник изображения и не хотим декодировать изображение сразу при создании объекта CGImageSource. В ситуации, когда у вас нет доступа к пути к источнику изображения, вы можете создать объект CGImageSource, используя инициализатор CGImageSourceCreateWithData().
  2. kCGImageSourceShouldCacheImmediately — этот флаг указывает, что декодировать изображение нужно именно в тот момент, когда мы запускаем процесс понижения дискретизации.
  3. kCGImageSourceCreateThumbnailWithTransform — установка этого флага в значение true очень важно для сохранения исходной ориентации.

Подводные камни (а как же без них)

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

Важные моменты

Что почитать ещё?

Exit mobile version