해당 게시글은 네이버부스트캠프에서 그룹 프로젝트를 진행하며 기록하는 내용입니다.
틀린 내용이 있을 수 있습니다.
저희 프로젝트는 WeTri입니다.
많이 구경하러 와주세요!
https://github.com/boostcampwm2023/iOS08-WeTri
이번 게시물에서는 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
}
🫡 결과
그 결과 앨범에서 가져오는 이미지 원본의 크기만큼 메모리 스파이크가 나타나는 현상을 최적화 할 수 있었습니다.
이로 인해 갑작스런 메모리 스파이크로 인해 사용자가 사용하고 있던 다른 앱을 강제로 종료시키는 등의 메모리로 인해 사용자가 앱을 사용하기 꺼려하는 상황을 예방할 수 있게 되었습니다.
'네이버 커넥트재단 부스트캠프 > 기술적도전' 카테고리의 다른 글
[WeTri][multipart/form-data] 회원가입 프로필 이미지 처리 (0) | 2023.12.12 |
---|---|
[WeTri][Cache] 캘린더 캐싱 with NSCache & FileManager (0) | 2023.12.01 |
댓글