티스토리 뷰
URL을 통해 여러 이미지를 받아오는 작업 많이 해보셨을 겁니다.
하지만 해당 이미지 하나당 크기가 엄청 크다면?
오늘은 위와 같이 큰 여러개의 이미지를 다운받아야 하는경우 사용자 경험 개선과 과부하되는 메모리 문제에 대해 해결하는 방법
다운 샘플링에 대해 블로깅 해보려고 합니다.
먼저 서론에서 언급했던 상황에서의 메모리는 아래와 같습니다.
이미지를 불러오는데도 당연히 오래걸리고
스크롤 했을때 버벅였던 현상이 있었습니다.
그 이유는 WWDC18 - Momory Deep Dive에서 나옵니다.
이미지의 경우 메모리 사용량은 width(너비), height(높이)에 의해 결정이 되는데요.
위와 같은 상황에서도 실제로 이미지 하나당 4000 * 3000 이런식으로 이미지 크기가 어마어마 했습니다.
문제 접근 방식
해당 문제에 대해 고민은 했던 내용은
'모바일에 보여질거고, 이미지인데 굳이 사이즈가 저렇게 크게 필요할까?'
'최적화된 이미지 크기로 바꿀 순 없을까?'
였습니다.
여러 검색결과 다운샘플링(downsampling) 이라는 키워드를 접할 수 있었습니다.
사전지식
들어가기전에 사용자가 사진을 보게되는 과정을 보고 갑시다. (이미지가 render 되기까지 과정)
- (UIImage) Image Load
- Data Buffer > Image Buffer로 Decode
즉, UIImageView는 말 그대로 View일 뿐이고,
UIImage가 Load하는 과정에서 Data Buffer로된 이미지를 Image Buffer로 디코딩하게 되며
이에따라 사용자가 사진을 볼 수 있게 됩니다.
메모리 사용량이 늘어나는 이유?
여기서 메모리 사용량이 늘어나는 이유는 UIImage가 디코딩된 이미지를 갖고 있기 때문인데요.
이를 해결하기 위해 디코딩되기 전에 이미지 데이터를 축소시키는 방법을 downsampling이라고 합니다.
downsampling의 과정은 아래 그림과 같아요.
기존 이미지 로그 과정에서 decode 이전에 한 단계가 더 생겼죠
이 부분에서 이미지 데이터를 축소시킨 후 디코딩 합니다.
아래는 WWDC에서 제공되는 다운샘플링 코드입니다.
func downsample(data: Data, to size: CGSize, scale: CGFloat) -> UIImage? {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else {
return nil
}
let maxDimension = max(size.width, size.height) * scale
let downsampleOptions = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimension
] as CFDictionary
guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
return nil
}
return UIImage(cgImage: downsampledImage)
}
코드를 하나씩 뜯어봅시다
ImageSourceOptions
이미지 소스 생성시 캐시를 사용할지 결정
ImageSource
이미지 데이터와 위 ImageSourceOptions에서 설정한대로 CGImageSource 생성
CGImageSource
이미지 데이터를 읽기 위해 사용되는 opaque(불투명) 타입
이미지 데이터를 load하는데 필요한 데이터버퍼 관리 및 사용가능 한 이미지로 변환하기 위해 해당 데이터에 대한 작업 수행
maxDimension
축소할 이미지의 최종 픽셀 설정
kCGImageSourceCreateThumbnailFromImageAlways
항상 imageSource가 원본 이미지를 썸네일로 만들지 결정
kCGImageSourceShouldCacheImmediately
이미지 생성 시 이미지 디코딩 및 캐싱을 수행할지 여부
kCGImageSourceCreateThumbnailWithTransform
이미지의 방향과 가로, 세로 비율에 맞게 조정할지 여부
kCGImageSourceThumbnailMaxPixelSize
썸네일 최대 픽셀사이즈 설정
CGImageSourceCreateThumbnailAtIndex
imageSource의 해당 index에 대해 썸네일 생성 (다운샘플링 이미지)
위와 같이 설정한 후 다운샘플링 적용 전, 후 차이를 살펴보겠습니다.
상황
List (tableView)에서 여러개의 이미지를 스크롤하면서 받아오는 상황
다운샘플링 적용 전 | 다운샘플링 적용 후 |
|
스크롤 할때 버벅임과 메모리차이가 확연하게 나는걸 볼 수 있습니다.
+ 추가
실제로 Kingfisher에서도 Downsapling 기능을 지원합니다
KFImage(URL(string: urlString))
.resizable()
.placeholder {
ProgressView()
}
.cancelOnDisappear(true)
.cacheMemoryOnly()
// MARK: - 성능문제 개선 (고해상도 이미지의 경우 썸네일만 먼저 받아오도록)
.setProcessor(DownsamplingImageProcessor(size: CGSize(width: 100, height: 100)))
.onFailureImage(KFCrossPlatformImage(systemName: NameSpace.ImageName.photo))
processor에서 DownsamplingImageProcessor를 이용해 다운샘플링을 하는 과정인데요.
해당 빌트인 메서드를 타고 들어가보면
WWDC에서 사용하던 코드 그대로 사용중인걸 볼 수 있습니다.
오늘은 다운샘플링에 대해 정리해봤는데
여러모로 꼭 필요한 기능인것 같다는 생각이 들었습니다.
WWDC를 주기적으로 봐야겠다는 생각도 들었고,
이런 기능들 하나하나 배워나가면서 사용자경험에 좋은 영향을 줄 수 있다면
선택이 아닌 필수라는 생각이 들었습니다 :)
참고
1. https://developer.apple.com/videos/play/wwdc2018/416/ (17:58~)
'iOS' 카테고리의 다른 글
[iOS] URLSession image upload (0) | 2024.11.24 |
---|---|
[iOS] Tuist App Extension (feat. WidgetExtension) (0) | 2024.10.25 |
[iOS] Tuist (feat. xcproj 충돌) (5) | 2024.09.02 |
[SwiftUI] @StateObject와 @ObservedObject의 차이점 (0) | 2024.07.25 |
[iOS] GCD - SerialQueue, ConcurrentQueue vs sync, async (0) | 2024.05.12 |
- Total
- Today
- Yesterday
- Swift 프로퍼티
- swift protocol
- swift (programmers)
- 2023년 회고
- Swift joined
- Swift inout
- iOS error
- 원티드 프리온보딩
- RTCCameraVideoCapturer
- Swift init
- swift reduce
- Swift Error Handling
- RIBs tutorial
- CS 네트워크
- removeLast()
- Swift 알고리즘
- Swift Leetcode
- swift 고차함수
- ios
- Swift 프로그래머스
- Swift
- Swift final
- Class
- Swift joined()
- Swift ModernRIBs
- Swift RIBs
- swift property
- Combine: Asynchronous Programming with Swift
- Swift 내림차순
- swift programmers
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |