본문 바로가기
네이버 커넥트재단 부스트캠프/기술적도전

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

by Joahnee 2023. 12. 12.

해당 게시글은 네이버부스트캠프에서 그룹 프로젝트를 진행하며 기록하는 내용입니다.

틀린 내용이 있을 수 있습니다. 
저희 프로젝트는 WeTri입니다.

많이 구경하러 와주세요!

https://github.com/boostcampwm2023/iOS08-WeTri

 

GitHub - boostcampwm2023/iOS08-WeTri: 우리가 함께 만드는, 트라이애슬론 🏃🏻 | 🏊‍♂️ | 🚴

우리가 함께 만드는, 트라이애슬론 🏃🏻 | 🏊‍♂️ | 🚴. Contribute to boostcampwm2023/iOS08-WeTri development by creating an account on GitHub.

github.com

 

이번 게시물에서는 WeTri에서 이미지를 전송할 때, 서버에서 413 Status Code가 발생한 이유이미지 관련 메모리 최적화를 했던 과정을 담아보려고 합니다 😌

 

🫠 문제 상황

Status Code : 413
errorMessage : file size too large

 

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

 

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

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

 

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

 

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

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

🤔 도입 과정

 

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

 

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

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정도 되던 앱의 평균적인 메모리 사용량이 확연하게 줄어드는 것을 확인할 수 있습니다.

 

 

 

🚨 문제 상황

 

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

 

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

 

🤔 원인

 

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

 

🚀 해결 과정

 

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

 

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과정을 

 

 

Data -> UIImage로 줄였습니다.

 

 

 

/// SignUpProfileViewController.swift
if 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
}

 

🫡 결과

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

 

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

 

댓글