저번글에서 이미지 캐싱에 대해 다뤘던 적이 있습니다.
추가적으로 학습을 하다보니
제가한 설계한 객체가 다소 객체지향에 어긋나고, 이미지 최적화에 있어 다소 미흡함을 알게되었습니다.

그래서 추가적인 학습을 통해
객체지향적 설계를 확립하고 다운샘플링 기술을 적용한 나만의 SDK를 만들어보자고 결정했습니다.
우선 앞선 iOS환경에서 바이너리 정보가 어떻게 이미지로 표출되는 지 이해할 필요가 있었습니다.
iOS의 이미지 프로세싱
우선 인터넷 서비스를 통해 혹은 로컬 저장소에서 불러온 이미지 정보는 모두 단순히 이진 데이터입니다.
해당 데이터들이 디코딩을 통해서 iOS내에서 시각화할 수 있는 이미지 버퍼(UIImage)로 변환되게됩니다.
최종적으로 시각화 형태의 데이터만이 지속적으로 메모리에 남게됩니다.
해당 과정을 통해서 최적화할 수 있는 부분은 다음과 같습니다.
1. 디코딩 과정에서 CPU연산을 줄일 수 없지 않을까?
2. 최종적으로 시각화된 이미지 버퍼를 줄여 메모리를 최적화할 수 있지 않을까?
1번의 경우 샘플링이라는 기술을 사용하여 데이터 버퍼상태의 이미지를 특정 픽셀공간에 알맞은 크기로
줄이거나 늘리는 기술을 의미합니다.
그중에서 시스템이 표현할 수 있는 픽셀수보다 더 많은 픽셀수를 가지는 이미지를
적절한 픽셀수로 줄여 최적화 하는 것이 다운 샘플링의 핵심입니다.
다운 샘플링을 통해 데이터 버퍼의 크기를 줄였다면 디코딩 과정이 최적화되고
산출되는 이미지 버퍼역시 기존보다 적은 공간을 차지하게 됩니다, 즉 2번 사항은 자연스럽게 최적화 됩니다.

이미지 다운 샘플링 코드레벨
코드레벨에서 어떻게 다운 샘플링이 일어나는 제가 작성한 코드를 기반으로 자세히 설명해 보겠습니다.
//#1. 우선 네트워크를 통해 다운로드한 Data인스턴스를 CGImageSource로 변경합니다.
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let imageSource = CGImageSourceCreateWithData(dataBuffer as CFData,
imageSourceOptions) else {
return nil
}
//#2. 이미지가 사용될 UIView영역을 의미하며 포인트 단위입니다.
let biggerLength = max(size.width, size.height)
//#3. 픽셀수는 해당 기기의 scale에 비래함으로 scale정보를 도출하여 포인트에 곱하여 픽셀 수를 도출합니다.
let scale = await UIScreen.main.scale
let maxDimensionInPixels = biggerLength * scale
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: false,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
] as CFDictionary
//#4. 도출한 옵션을 사용하여 이미지 썸네일을 생성합니다.(= 다운샘플링된 이미지 생성)
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0,
downsampleOptions) else {
return nil
}
let image = UIImage(cgImage: downsampledImage)
return image
해당 과정에서 maxDimensionsInPixel을 산출하는 과정을 추가적으로 설명해 보겠습니다.
우선 해당 값을 기준올 정사각형 공간을 만들고
해당 공간에 원본 이미지 유실 없이 들어갈 수 있도록 샘플링하는 매커니즘 입니다.
따라서 샘플링을 목적으로 전달한 포인트 공간의 너비와 높이중 더 큰 값을 사용합니다.
얼마나 최적화 되었을까?
100개의 이미지를 로드하는 그리디 뷰를 만들고 메모리공간을 얼마나 차지하는 지 확인해봤습니다.
Instruments를 사용해 메모리 점유를 테스트 해보니 결과는 아래와 같다았다.
먼저 다운 샘플링 이전이다.
다운 샘플링을 적용한 결과이다.

시스템이 차지하는 메모리를 제외하면 2배이상의 공간차이를 보인다.
시연 화면에서 볼 수 있듯 테스트에 사용한 이미지는 모두 단색 이미지이지만, 고해상도 이미지를 사용할 경우
성능상에 크리티컬한 차이가 발생할 것이라고 생각된다.
객체지향적 설계에 대해
제가 이부분을 많이 고려한 이유는 최근에 "객체지향의 사실과 오해, 조영호"를
굉장히 감명깊게 읽었기 때문입니다.
저희가 객체지향을 사용하는 이유는 단순히 코드를 묶기 위해서가 아니라
복잡한 현실 혹은 코드세상을 분할과 추상화를 통해 간단하게 만들어 이해를 돕기 위해서죠

해당 측면에서 이전글에서 소개했던 객체는 매우 복잡하고 유지보수가 힘든 객체란 것을 알 수 있습니다.
따라서 이번 SDK를 개발할 때는 전체적인 시나리오(UseCase)를 여러 책임으로 분할하고
해당 책임을 수행할 객체들 및 객체들이 소통할 때 사용하는 메세지에 주목하여 설계를 해보았습니다.
도출한 객체 다이어 그램은 다음과 같습니다.
해당 객체들은 모두 역할에 따라 도출한 인터페이스를 따르는 구체타입글로 구성되어 있어
추후 확장 및 변경에 매우 유연한 구조 역시 가지고 있습니다.
해당 SDK에 대한 자세한 설명은 아래 깃허브 링크를 참고하시는 것을 추천합니다!
리드미에 시연 영상 및 테스트 코드를 통해 기능이 정상적으로 동작함을 확인하실 수 있습니다.
SimpleImageProvider – Swift Package Index
SimpleImageProvider by Junyeong choi on the Swift Package Index – 간편한 이미지 캐싱 라이브러리입니다.
swiftpackageindex.com
회고
시장에는 이미 KingFisher라는 훌룡한 이미지 라이브러리가 있지만,
이미지 전처리를 직접 해보고 성능을 테스트하는 경험을 통해 iOS환경에 대한 이해와 최적화에 대한 중요성을 느낄 수 있었다.
모두 한번은 이런 라이브러리를 만들어 보는 것을 추천한다.
감사합니다. 🙇♂️
'iOS공통' 카테고리의 다른 글
[Swift] why에 집중한 Swift concurrency, Sendable, Actor ... (1) | 2025.02.25 |
---|---|
[iOS] fastlane을 사용하며 겪은 일들(with Tuist) (0) | 2025.01.02 |
[iOS] Fun하지 않은 정적/동적 라이브러리와 Symbol (3) | 2024.10.31 |
[iOS] 케어밋 프로젝트 기술회고2: 재사용가능한 UI에 대해서 (0) | 2024.10.26 |
[iOS] 케어밋 프로젝트 기술회고1: 클린아키텍처 (4) | 2024.10.10 |