티스토리 뷰

728x90

URL을 통해 여러 이미지를 받아오는 작업 많이 해보셨을 겁니다.

하지만 해당 이미지 하나당 크기가 엄청 크다면?

 

오늘은 위와 같이 큰 여러개의 이미지를 다운받아야 하는경우 사용자 경험 개선과 과부하되는 메모리 문제에 대해 해결하는 방법

다운 샘플링에 대해 블로깅 해보려고 합니다.


 

먼저 서론에서 언급했던 상황에서의 메모리는 아래와 같습니다.

이미지 사이즈가 큰 경우 여러개의 이미지를 받아올 때

 

 

이미지를 불러오는데도 당연히 오래걸리고 

스크롤 했을때 버벅였던 현상이 있었습니다.

 

 

그 이유는 WWDC18 - Momory Deep Dive에서 나옵니다.

메모리 사용량은 파일 크기가 아니라 이미지의 사이즈와 관련이 있습니다.

 

이미지의 경우 메모리 사용량은 width(너비), height(높이)에 의해 결정이 되는데요.

위와 같은 상황에서도 실제로 이미지 하나당 4000 * 3000 이런식으로 이미지 크기가 어마어마 했습니다.

 

문제 접근 방식

 

해당 문제에 대해 고민은 했던 내용은

'모바일에 보여질거고, 이미지인데 굳이 사이즈가 저렇게 크게 필요할까?'

'최적화된 이미지 크기로 바꿀 순 없을까?

였습니다.

 

여러 검색결과 다운샘플링(downsampling) 이라는 키워드를 접할 수 있었습니다.

 

 

사전지식

 

들어가기전에 사용자가 사진을 보게되는 과정을 보고 갑시다. (이미지가 render 되기까지 과정)

 

  1. (UIImage) Image Load
  2. Data Buffer > Image Buffer로 Decode

 

즉, UIImageView는 말 그대로 View일 뿐이고,

UIImage가 Load하는 과정에서 Data Buffer로된 이미지를 Image Buffer로 디코딩하게 되며 

이에따라 사용자가 사진을 볼 수 있게 됩니다.

 

메모리 사용량이 늘어나는 이유?

 

여기서 메모리 사용량이 늘어나는 이유UIImage가 디코딩된 이미지를 갖고 있기 때문인데요. 

 

이를 해결하기 위해 디코딩되기 전에 이미지 데이터를 축소시키는 방법downsampling이라고 합니다.

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~)

2. https://felix-mr.tistory.com/11

3. https://velog.io/@dev_jane/UICollectionView-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B2%98%EB%A6%AC-downsampling

728x90