Skip to content

[메모리 최적화] 이미지 크기 최적화 및 메모리 스파이크 해결과정

Vardy edited this page Dec 12, 2023 · 5 revisions

이번 게시물에서는 WeTri에서 이미지를 전송할 때, 

서버에서 413 Status Code가 발생한 이유와 이미지 관련 메모리 최적화를 했던 과정을 담아보려고 합니다 😌

🫠 문제 상황

Status Code : 413 errorMessage : file size too large

백엔드에서는 누군가 큰 이미지의 데이터를 보내면 서버가 멈출 가능성이 있다는 이유로 이미지 최대용량을  10MB로 제한했습니다.

그래서 테스트도 해볼 겸 애플의 기본 이미지를 보내봤는데, 아래와 같은 문제가 발생했습니다.

(처음에는 413으로 왔는데 이후 API가 수정되어 400으로 들어오게 되었습니다.)

https://blog.kakaocdn.net/dn/bZoa3K/btsBHTdFGPi/S9TlojFNMVszmO19Tc7YDk/img.png

앱에서 이미지 크기를 어떻게든 줄여서 주셔야 된다고 하셨는데요 😂

실제 UIImage를 Data로 변경해서 Server로 전송해도 그 크기가 .pngData()의 경우 줄어듦이 거의 없습니다.

(나중에 알게된 사실인데 jpeg로 압축하는 메소드도 존재하더라구요 🤣)

어찌됐든, 프로필 이미지 원본을 메모리에 들고있는 것만으로도 메모리 크기가 150MB 정도를 차지하기 때문에 이미지의 크기를 줄일 방법이 필요했습니다.

https://blog.kakaocdn.net/dn/y3mac/btsBQRZZPTc/qYUmauGmfkAEcAAjoqqyD1/img.png

그래서 어떻게 이미지를 최적화해서 서버에서 원하는 대로 보내드릴 수 있을지를 고민하고 자료를 찾아보던 중 WWDC에서 ImageDownsampling을 소개하는 영상을 보게되었습니다.

ProfileImage의 경우 사용자들은 대부분 스마트폰으로 촬영한 사진을 사용할 것입니다.

제가 사용하는 iPhone-11 기준으로 저장된 이미지 픽셀수가 평균적으로 3024 * 4032 * 4 = 48,771,072 byte 대략 48MB가 나옵니다.

저희 앱에서는 프로필 이미지를 자세히 보는 기능이 없고,

디자인 상에서도 제일 크게 보여주는 이미지 크기가 100 * 100 입니다.

때문에 화질 관련해서는 흐리게 보이는 정도만 아니라면 상관이 없다고 판단했습니다.

🤔 도입 과정

팀원이 다 같이 사용할 수 있도록 Image를 Downsampling 할 수 있는 모듈을 만들었습니다.

https://blog.kakaocdn.net/dn/KUJpH/btsBG6j3wLg/D8uMsV9BG5KhkE4dra6bI0/img.png

아래는 구현한 코드입니다.

import UIKit

public extension UIImage {
  func downsampling(size: CGSize, scale: Scale) throws -> UIImage {
    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
    guard let data = pngData(),
          let imageSource = CGImageSourceCreateWithData(
            data as CFData,
            imageSourceOptions
          )
    else {
      throw ImageDownsamplingError.failImageSource
    }
    let thumbnailMaxPixelSize = max(size.width, size.height) * scale.rawValue
    let downsampleOptions = [
      kCGImageSourceCreateThumbnailFromImageAlways: true,
      kCGImageSourceCreateThumbnailWithTransform: true,
      kCGImageSourceThumbnailMaxPixelSize: thumbnailMaxPixelSize,
    ] as CFDictionary

    guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
      throw ImageDownsamplingError.failThumbnailImage
    }
    return UIImage(cgImage: downsampledImage)
  }
}

📚 결과

해당 과정을 통해 프로필 이미지 하나만 추가해도, 메모리소비가 192MB정도 되던 앱의 평균적인 메모리 사용량이 확연하게 줄어드는 것을 확인할 수 있습니다.

https://blog.kakaocdn.net/dn/cxbVvV/btsBHsAgnN7/Vyb4ghur4uKkKpzsgrxKZ1/img.png

🚨 문제 상황

혹시 위의 사진에서 이상한점을 찾으셨나요?

메모리 스파이크 현상이 일어났습니다. 🤯

🤔 원인

UIImagePickerController(앨범 or 카메라)에서 이미지를 가져오는 과정에서 메모리 스파이크 현상이 발생한것이었습니다.

🚀 해결 과정

UIImagePickerController에서 데이터를 InfoKey 요소에서 가져오는데요.

https://blog.kakaocdn.net/dn/Wtld9/btsBJiK5kwD/pyJEFovCVEXKksRsc69q9K/img.png

UIImage를 가져와서 downsampling하는 과정을 거쳤습니다.

if let image = try (info[.originalImage] as? UIImage)?.downsampling(size: profileImageButton.profileSize, scale: .x3),
   let imageData = image.pngData() {
  profileImageButton.image = image
  imageSetSubject.send(imageData)
}

여기서 메모리 스파이크 현상을 보고 2가지 가정을 할 수 있었습니다.

1️⃣ 공식 문서에 UIImage라고 적혀있으니, info[.originalImage]에서 불러올 때, DataBuffer -> Image Buffer로 변경되면서 메모리 스파이크 현상이 발생하는것

2️⃣ UIImage로 캐스팅할 때 메모리 스파이크 현상이 발생하는것

일반적인 info[.originalImage]를 메모리에 저장하고 메모리를 확인해본 결과 실험해본 결과는 메모리 스파이크는 2번에서 발생하는 것이었습니다.

그래서, 기존의 Data -> UIImage -> Data -> UIImage의 Downsampling과정을

https://blog.kakaocdn.net/dn/kUiq3/btsBUKMOldj/KJrc9TTOTjHAsZdZ4RtFA1/img.png

Data -> UIImage로 줄였습니다.

https://blog.kakaocdn.net/dn/dasvWr/btsBHRN3oEn/MAVfhyGzHqcgCVdH9m13g0/img.png

/// SignUpProfileViewController.swiftif let url = info[.imageURL] as? URL {
	let imageData = try Data(contentsOf: url)
    let image = try imageData.downsampling(size: profileImageButton.profileSize, scale: .x3)
    profileImageButton.image = image
    guard let downsampledData = image.pngData() else {
      return
}

🫡 결과

그 결과 앨범에서 가져오는 이미지 원본의 크기만큼 메모리 스파이크가 나타나는 현상을 최적화 할 수 있었습니다.

https://blog.kakaocdn.net/dn/b61MRm/btsBQX7pMMw/0BduchS9S82ay0M7KygkV0/img.png

이로 인해 갑작스런 메모리 스파이크로 인해 사용자가 사용하고 있던 다른 앱을 강제로 종료시키는 등의 메모리로 인해 사용자가 앱을 사용하기 꺼려하는 상황을 예방할 수 있게 되었습니다.


Clone this wiki locally