본문 바로가기
개린이 이야기

Kingfisher 라이브러리에 관하여

by iOS 개린이 2023. 5. 28.

Kingfisher란?

-Swift로 작성된 이미지 캐싱 라이브러리로, 웹 상의 이미지를 비동기적으로 다운로드하고 캐시하는데 사용된다.

이미지 다운로드 작업, 캐싱 작업 등을 쉽게 처리해주기 때문에 개발자에게 편리한 라이브러리다.

 

Kingfisher의 주요 기능

1. 비동기 이미지 다운로드

-Kingfisher는 이미지 다운로드 작업을 비동기적으로 수행한다. 이로 인해 UI가 멈추거나 지연되는 일 없이 원활하게 작동한다.

 

2. 이미지 캐싱

-이미지는 한 번 다운로드 된 후에는 디스크와 메모리에 캐시된다. 이로 인해 같은 URL로부터 이미지를 여러 번 요청하더라도, 캐시된 이미지를 사용하기 때문에 앱의 반응성이 향상된다.

 

3. 이미지 처리

-Kingfisher는 다양한 이미지 처리 기능을 제공한다. 예를 들어 이미지를 변형하거나, 필터를 적용하거나, 이미지를 원하는 형태로 자르는 등의 작업이 가능하다.

 

 

Kingfisher의 작업과 이미지 관리 최적화 방법.

1. 비동기 작업 관리

-Kingfisher는 DispatchQueue와 같은 동시성 프로그래밍 도구를 사용하여 여러 이미지 다운로드 작업을 동시에 처리한다. 여기서 메인스레드를 차단하지 않고 여러 작업을 동시에 처리할 수 있어 UI가 멈추는 일이 없는 것이다.

 

2. 이미지 다운로드 작업의 순서

-기본적으로 이미지 다운로드 작업을 등록된 순서대로 처리한다. 먼저 요청된 작업이 먼저 처리되는 FIFO방식으로, 작업이 겹치지 않게 하여 네트워크 요청을 관리한다.

 

3. 메모리 관리

-Kingfisher는 메모리 캐시에 저장된 이미지가 많아지면 자동으로 일부 이미지를 제거하여 메모리 사용량을 관리한다.

이를 통해 메모리 부족 문제를 방지하고, 앱의 성능을 최적화하는데 도움이 된다.

직접 Memory Storage나 Disk Storage에 접근하여 캐싱되는 아이템의 수를 제한하거나, 만료일을 지정, 만료된 아이템을 삭제하는 주기를 지정 등의 작업으로 세밀한 관리도 가능하다.

 

4. 에러 처리

Kingfisher는 이미지 다운로드나 처리 과정에서 발생할 수 있는 다양한 에러상황을 편리하게 처리해준다. 

예를 들어 해당 URL로 이미지 다운로드 작업을 하는 과정에서 네트워크 연결 문제, 서버 응답 오류, 유효하지 않은 URL 등 다양한 이유로 작업을 실패할 수 있죠? 

Kingfisher는 KingfisherError 열거형을 통해 상세한 에러 유형을 제공하는데, 여기에는 .networkError, .responseError, .casheError 등의 케이스가 포함되어 있다.

에러 유형에 따라 개발자는 사용자에게 에러 메시지를 표시하거나, 대체 이미지를 보여주는 등의 적절한 조치를 취할 수 있다.

 

 

Kingfisher의 사용

extension UIImageView {
    func setImage(with urlString: String, width: CGFloat, height: CGFloat) {
        self.kf.indicatorType = .activity  //이미지 다운로드 작업이 진행중일 때, 이미지 뷰에 인디케이터 설정.
        let processor = DownsamplingImageProcessor(size: CGSize(width: width, height: height)) //이미지뷰 크기에 맞게 이미지다운샘플링
        let retry = DelayRetryStrategy(maxRetryCount: 1, retryInterval: .seconds(3)) //이미지 다운로드를 실패할 시 재실행
        let options : KingfisherOptionsInfo = [
            .scaleFactor(UIScreen.main.scale),
            .processor(processor),
            .transition(.fade(0.4)),
            .cacheOriginalImage,
            .retryStrategy(retry)
        ]
        
        ImageCache.default.retrieveImage(forKey: urlString, options: options) { [weak self] result in
            switch result {
                
            case .success(let cacheResult):
                
                if let image = cacheResult.image {
                    self?.image = image
                    
                }else{
                    self?.fetchImageURL(from: urlString) { result in
                        
                        if case .success(let url) = result {
                            self?.kf.setImage(with: url, options: options)
                            
                        }else if case .failure(let error) = result {
                            print("Error : \(error.localizedDescription)")
                        }
                    }
                    
                }
                
                
            case .failure(let err): //이미지 검색작업의 실패.
                print("Error: \(err.localizedDescription)")
            }
        }
        
    }
    
    func fetchImageURL(from url: String, completion: @escaping (Result<URL, Error>) -> Void) {
        let storage = Storage.storage()
        storage.reference(forURL: url).downloadURL { (url, error) in
            if let e = error {
                completion(.failure(e))
                
            }else if let url = url{
                completion(.success(url))
            }
        }
    }
}

 

필자는 KingFisher를 사용하여 이미지 다운로드 작업을 수행할 때 위 코드와 같이 사용한다.

코드에 대해서 하나씩 살펴보자.

 

1. 인디케이터 설정.

self.kf.indicatorType = .activity  //이미지 다운로드 작업이 진행중일 때, 이미지 뷰에 인디케이터 설정.

 

-주석 그대로 사용자에게 로딩 중이라는 것을 알리는 작업.

 

 

2. 이미지의 다운샘플링.

let processor = DownsamplingImageProcessor(size: CGSize(width: width, height: height)) //이미지뷰 크기에 맞게 이미지다운샘플링

 

-이미지뷰의 크기에 비해 이미지의 크기에 크다면 그만큼 메모리의 낭비가 발생한다.

이미지의 사이즈를 이미지뷰의 사이즈에 맞게 줄여주어야 하는데, 이를 위해 KingFisher에서 DownsmaplingImageProcessor라는 프로세서를 제공한다.

이 프로세서는 이미지를 다운로드하고 캐시에 저장될 때 사용되며,

프로세서에 의해 처리된 이미지는 다음에 요청되어도 같은 방식으로 처리되어 제공된다.

 

메모리를 최적화하면서 이미지를 처리하기 위해 이미지 리사이징, 이미지 다운샘플링 등 무엇을 사용할지에 대한 고민이 있었다.

 

리사이징은 이미지의 사이즈를 줄이지만, 해상도를 그대로 유지하므로 좋은 화질을 유지할 수 있고,

다운샘플링은 이미지의 해상도를 줄여, 메모리의 사용량을 많이 절약할 수 있다.

 

하지만 리사이징은 여전히 메모리 사용량이 크고, 다운샘플링은 이미지 퀄리티가 떨어지는 단점이 있어서

여러 방법을 시도해본 결과, 다운샘플링을 사용하기로 결정했다.

이유는 적절한 해상도를 유지하면서 메모리의 최적화를 가져오는 것이 좋을 것이라고 판단했다. 

어떤 방법을 사용할 지는 상황에 맞게 적절하게 선택하는 것이 좋다. 

 

 

3. 재시도

let retry = DelayRetryStrategy(maxRetryCount: 1, retryInterval: .seconds(3)) //이미지 다운로드를 실패할 시 재실행

 

-주석 그대로 이미지 다운로드에 실패했을 경우, 재시도 하는 작업을 의미 한다. (3초의 딜레이를 가지고 최대 1번 재시도 한다.)

 

 

4. 옵션

let options : KingfisherOptionsInfo = [
            .scaleFactor(UIScreen.main.scale),
            .processor(processor),
            .transition(.fade(0.4)),
            .cacheOriginalImage,
            .retryStrategy(retry)]

 

-먼저 "KingfisherOptionsInfo"는 Kingfisher 라이브러리에서 이미지 처리와 관련된 다양한 옵션을 설정할 수 있는 타입이다.

 

1) .scaleFactor(UIScreen.main.scale)

이미지를 처리하고 표시할 때, 사용할 스케일 팩터를 설정하는 옵션이다. 스케일 팩터는 1x, 2x, 3x 등과 같이 화면의 물리적인 크기와 해상도 사이의 비율을 나타낸다. 코드에서는 스케일 팩터를 현재 디바이스의 크기에 맞게 설정한다.

스케일 팩터를 설정하는 이유는 크게 두 가지이다.

첫 번째는 같은 크기의 이미지라도 스케일 팩터에 따라 픽셀의 밀집도가 다르기 때문에 화면의 스케일 팩터에 맞게 이미지를 처리하면, 이미지의 품질을 높여줄 수 있다.

두 번째는 화면의 스케일 팩터에 맞는 이미지를 사용하여 필요한 만큼의 메모리만 사용하는 것이다. 예를 들어 스케일 팩터가 2x인 화면에서 3x 이미지를 사용하면, 이미지 처리에 필요한 메모리가 그만큼 증가한다. 따라서 화면의 스케일 팩터에 맞는  이미지를 사용하여 메모리를 최적화 하는 것.

 

2) .processor(processor)

이미지를 다운로드 한 후에 이미지에 적용할 프로세서를 설정하는 옵션. 우리는 DownsmaplingImageProcessor 사용함.

 

3) .transition(.fade)

이미지가 이미지 뷰에 나타날 때, 적용할 애니메이션 효과 옵션.

 

4) .cacheOriginalImage

원본 이미지를 캐시에 저장할지 여부를 설정하는 옵션. 

이 옵션을 설정하면, 프로세서에 의해 처리된 이미지뿐만 아니라 원본 이미지도 캐시에 저장된다.

 

5) .retryStrategy(retry)

위에서 설명했듯이 다운로드에 실패했을 경우 재시도하는 옵션.

 

 

5. 캐시에서 이미지 찾기

ImageCache.default.retrieveImage(forKey: urlString, options: options)

 

-이미지 캐시에서 지정된 키(forKey)에 해당하는 이미지를 검색하는 작업이다. 해당 url로 다운로드 받은 이미지가 먼저 캐시에 존재하는지 여부를 판단한다. 이미지가 유효하다면 해당 이미지를 사용하고, 유효하지 않다면 FireStorage에 저장된 이미지 URL을 가져온다.

 

 

6. 이미지 다운로드.

kf.setImage(with: url, options: options)

 

-이미지를 비동기적으로 다운로드하고 이미지 뷰에 설정하는 작업이다.

주어진 URL에서 이미지 다운로드가 완료되면 option에 맞게 이미지를 처리하고, 결과물을 이미지 뷰에 설정한다.

 

사실 이 메서드는 내부적으로 캐시를 확인하는 작업도 수행하기 때문에

우리의 코드처럼 "ImageCache.default.retrieveImage" 를 통해 캐시에서 이미지를 찾는 작업을 꼭 수행할 필요는 없다.(상황에 맞게 적절히 사용하자.)

 

 

ResizingImageProcessor와 DownsamplingImageProcessor의 차이

-두 프로세서의 결정적인 차이는 이미지의 리사이징 시점과 방법에 있다.

 

1. ResizingImageProcessor

이 프로세서는 원본 이미지를 메모리에 로드한 후에 이미지 크기를 조정한다. 따라서 원본 이미지가 크면 클수록 상당히 많은 메모리를 사용하게 된다. 이미지의 크기를 조정하는 과정에서도 추가적인 메모리를 사용하기 때문에 전체적인 메모리 사용량이 높다. 

하지만 원본 이미지의 모든 픽셀을 메모리에 로드하기 때문에 이미지의 퀄리티가 높다. 

 

2. DownsamplingImageProcessor

이 프로세서는 이미지의 로드와 다운샘플링이 동시에 수행된다. 즉, 원본 이미지에서 필요한 픽셀만 선택하고 나머지는 제거하는 작업을 통해 필요한 정보만을 메모리에 로드한다. ResizingImageProcessor에 비해 메모리 사용량이 크게 감소하는 효과를 가진다.

 

따라서 상황에 맞게 절적한 프로세서를 사용하자