서론
moti 앱은 사진이 핵심 콘텐츠인 앱입니다. 모든 곳에서 사진을 보여줘요.
그렇기 때문에 이미지에 사용하는 메모리가 많습니다.
이번 개발일지에서는 이미지에 소비되는 메모리를 줄이기 위해 도입한 다운 샘플링과 메모리 최적화 과정을 다뤄보겠습니다.
특별하진 않지만 매우 중요한 고민 과정이었습니다.
다운 샘플링이 필요한 이유
다운 샘플링을 적용하기 전, 팀원에게 다운 샘플링이 무엇인지, 다운 샘플링이 왜 필요한지 설명해야 했습니다.
저는 다운 샘플링을 적용한 경험이 있었지만, 다른 팀원은 그렇지 않았거든요.
지금도 잘 돌아가는데 추가로 무언갈 작업해야 하나?라는 생각이 들 수도 있으니 제대로 된 설명을 해야 했습니다.
먼저 다운 샘플링을 적용하지 않고 앱을 실행해 봤습니다.
메모리와 관련된 내용이라 메모리 측정만 진행했어요.
표시하는 이미지 크기가 크다 보니 상상 이상으로 높더라고요;;
(이미지 표시에 쓰이는 메모리와 관련된 자세한 내용은 Image and Graphics Best Practices 포스팅을 참고해 주세요.)
조금 더 많은 데이터를 표시하니 앱이 버벅거리며 강제 종료까지 되었습니다.
또 다른 이유로 사용자 경험 개선이 있었습니다.
WWDC에서 더 나은 사용자 경험을 위해 앱에서 사용하는 메모리를 줄여야 한다고 말하고 있습니다.
앱에서 사용하는 메모리가 많아지면 백그라운드에서 대기 중에 앱이 종료되고, 그러면 전반적인 iOS 사용자 경험이 줄어들기 때문입니다.
moti 앱을 개발하며 사용자 경험을 높이기 위해 노력하기로 결심한 상태라서 이것도 중요한 이유로 작용했습니다.
이렇게 두 가지 이유로 저희는 다운 샘플링이 필요했습니다.
다운 샘플링 결과
다운 샘플링은 Core Graphics를 이용해 진행했습니다.
public func downsampling(to targetSize: CGSize, scale: CGFloat = 1) -> UIImage? {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let imageSource = CGImageSourceCreateWithData(self as CFData, imageSourceOptions) else { return nil }
let maxDimension = Swift.max(targetSize.width, targetSize.height) * scale
let downsamplingOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimension
] as CFDictionary
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsamplingOptions) else {
return nil
}
return UIImage(cgImage: downsampledImage)
}
WWDC에서 소개된 코드를 기반으로 작성했어요.
이렇게 다운 샘플링을 진행한 결과,
13배나 차이가 날 정도로 많은 양의 메모리가 절약되었습니다.
생각보다 훨씬 큰 차이라서 저도 당황을 했어요 ;;
측정 당시에는 서버에서 섬네일 제작을 하지 않고, 원본 이미지를 전달해 줘서 더 큰 차이가 발생했던 거 같습니다.
이렇게 다운 샘플링의 중요성을 다시 한번 깨달을 수 있었습니다.
메모리 캐시 개선
다운 샘플링을 적용한 뒤 여러 시나리오에서 메모리 측정을 했습니다.
그런데 특정 시나리오에서 예상한 메모리보다 훨씬 높게 측정되는 이슈가 있었습니다.
이슈가 있는 시나리오는 홈 화면에서 상세 화면으로 넘어갈 때입니다.
또 하나의 이미지를 표시하기 때문에 메모리가 어느 정도 올라갈 거라고 예상은 했지만, 예상치 보다 더 많이 올라갔어요.
올라갔던 이유는 메모리 캐시 때문이었습니다.
서버에서 받은 원본 이미지 데이터를 메모리에 캐싱하기 때문에 예상보다 더 메모리를 소비하고 있었어요.
팀원과 이 부분에 대해 깊게 고민했습니다.
메모리 캐시 때문이라면 저희가 예상을 못했지만, 오류는 아닌 상황입니다.
그럼에도 고민한 이유는 이 시나리오에서 메모리 캐시가 필요할까? 의문이 있었기 때문입니다.
메모리 캐시를 사용하는 이유
그래서 조금 더 근본적인 시선으로 접근해 보기로 했습니다.
메모리 캐시를 왜 사용할까? 에서 고민을 시작해 봤어요.
메모리 캐시를 적용한 이유는 이 이미지를 다시 보여줄 때 빠르게 표시하기 위해서입니다.
그래서 메모리 캐싱은 "다시" 보여줄 때 의미가 있는 작업이라고 생각했습니다.
하지만 위 시나리오는 자주 발생하는 시나리오는 아닙니다.
도전 기록의 상세 정보를 자주 보지는 않을 거라고 예상했어요.
특히 한 번 본 기록을 다시 본다? 이건 거의 발생하지 않는 시나리오로 예상이 됐습니다.
개선 방법
위와 같은 이유로 원본 이미지에 대해서는 메모리 캐싱을 하지 않기로 했습니다.
다만, 혹여나 다시 보는 케이스를 위해 디스크 캐싱은 그대로 진행을 하기로 했습니다.
디스크 캐시는 메모리 캐시에 비해 느리긴 하지만, 가끔 발생하는 시나리오에 대응하기엔 충분하다고 판단했어요.
개선 결과
원본 이미지 메모리 캐싱을 제거한 결과, 약간의 메모리를 더 절약할 수 있었습니다.
큰 의미가 있는가? 하면 그건 아니라고 생각합니다.
그럼에도 다운 샘플링만으로 만족하지 않고, 더 나은 결과를 위해 고민한 경험이었습니다.
메모리 개선 피드백
메모리 캐시 개선 작업을 멘토님께 설명한 결과, 예상과 달리 부정적인 피드백을 받았습니다.
고민한 내용에 대해선 긍정적이었지만, 구현 방법에 대해 부정적인 피드백을 주셨어요.
왜냐하면 이 메모리 개선을 Jeongfisher라는 별도의 라이브러리를 사용했기 때문입니다. (Jeongfisher는 제가 개발한 SPM 라이브러리입니다.)
본인이 만든 라이브러리등을 프로젝트에 사용하는 것은 (개인별 차이가 있겠으나) 다음에 어딘가 사 옹할 경우에, 이것을 평가하는 사람 입장에서는 내부를 한번 더 뜯어봐야 한다는 러닝커브가 존재합니다. 상대적으로 이해하기 어려운 동작과 코드가 되며, 불필요한 wrapping으로 느껴지기도 합니다. (Extension 정도면 좋을 수도 있습니다).
...
발표 및 사전 코딩과제의 개념에서 본다면 모든 코드를 한 레파지토리 내에서 확인할 수 있는 게 더욱 좋습니다.
네이버 부스트캠프는 누군가에게 평가받는 과제에 가깝기 때문에 별도의 라이브러리보다 직접 코드를 작성하는 게 더 좋았을 수 있다는 의견이셨어요.
구현을 할 당시에는 전혀 생각하지 못한 내용이라 너무나 감사한 피드백이었습니다.
SPM을 사용해서 기술적으로 큰 메리트가 있는 것도 아닌데 평가자를 생각하지 않은 선택이었구나 깨달을 수 있었어요.
외부 모듈 도입했거나 유지보수하는 담당자가 퇴사하면 유지보수가 더 어렵게 되고 문제가 생기는 경우가 많이 있습니다.
일부 스타트업에서는 문제가 되지 않을 수도 있고, 각자의 성향에 따라 다르게 판단될 수도 있는 문제이지만
규모가 있는 기업에서는 좋지 않은 시각이거나, 아니면 기술유출로 판단하는 경우도 있습니다(업무와 관련된 코드를 회사 환경이나 회사 장비에서 작성했음에도 불구하고 외부에 공개).
또한 현업 상황을 공유해 주시며 취준생으로서는 생각하지 못했던 피드백도 받았습니다.
개인 프로젝트만 진행하던 저에게 또 다른 시야를 전달해 주셔서 너무 감사했습니다.
제가 지금 하는 활동이 무엇인지를 한 번 더 생각해 보며 상황에 맞는 기술적 선택을 하는 것도 중요하구나 배운 경험이었습니다.
마무리
메모리 개선을 하면서 기술적인 경험은 물론 어떤 기술적 선택을 하는 게 좋을지 깨닫기도 했습니다.
만약 네이버 부스트캠프에 참여하지 않고 혼자서 공부했다면 이런 점은 모르지 않았을까? 생각도 들면서,
네이버 부스트캠프에 참여하길 잘했다고 느껴졌어요.
피드백을 주신 멘토님께 다시 한번 감사의 말씀을 드리면서 이번 포스팅을 마치겠습니다.
다음 포스팅은 보이스 오버에 대한 내용입니다.
보이스 오버를 적용한 이유는 무엇이고, 적용을 하며 겪은 이슈를 공유드리겠습니다.
감사합니다.
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.