이미지 라이브러리 비교 분석
안녕하세요! 미니입니다. 오랜만에 글을 작성하게 되었네요 ㅜㅜ
오늘은 프로젝트를 수행하면서 비교하게 된 iOS 이미지 캐싱 라이브러리들에 대해서 비교해보려고 합니다.
비교를 하기전에 Cache라는 것이 무엇이고, 왜 쓰는지에 대해서 고민을 해 볼 필요가 있습니다.
Cache
캐시 : 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다. 캐시는 캐시의 접근 시간에 비해 원래 데이터를 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용한다. 캐시에 데이터를 미리 복사해 놓으면 계산이나 접근 시간없이 더 빠른 속도로 데이터에 접근할 수 있다.
출처 : 위키 백과 (https://ko.wikipedia.org/wiki/캐시)
위에 있는 정의는 위키 백과에 나와있는 내용이에요. 간단하게 말하지면 “저장해두자” 입니다. ㅎㅎ
캐시는 데이터에 대한 접근성을 높이는 방법 중 하나입니다. 사용자가 앱을 사용하면서 이미지나 데이터를 서버에서 받아오는 작업을 하게 될 것입니다. 이때, 서버에서 받아오는 작업은 많은 리소스를 필요하게 되는데, 받아온 데이터를 메모리에 저장하거나 로컬 시스템에 저장하고 같은 요청이 발생할 때 로컬에서 데이터를 보내는 것을 이야기 합니다. 캐시라는 개념은 하드웨어 (CPU, Memory)에서 파생된 개념입니다.
간단하게 캐시 개념을 알았으니 비교를 해볼게요. 프로젝트를 수행하면서 고민하게 된 라이브러리들을 기준으로 비교를 해봤습니다. Kingfisher와 Nuke에 대해서 비교해보려고 합니다. (SDWebImage와 AlamofireImage 같은 라이브러리들도 존재합니다. 편식 안됩니다. ㅋㅋㅋㅋㅋ)
테스트를 수행한 방법은 실제 이미지를 로드하는 과정에서 CPU와 메모리가 증가률을 기록했고, 공식 문서들의 참고하였습니다.
Kingfisher
성능
첫번째로 Kingfisher입니다. 아래의 표는 실제 이미지를 불러오는 과정을 반복적으로 수행시킨 그래프와 증가량입니다. CPU는 이미지를 불러오기 전보다 최대 65%p 가 증가한 것을 볼 수 있고, 이미지를 불러오면서 생성된 백그라운드 쓰레드가 정리 되지 않는 것을 확인할 수 있습니다. 메모리 증가량은 32MB 가량 증가하게 되었습니다.
구분 | 실행 전 | 실행 후 | 비고 |
CPU | 0% | 65% | 쓰레드가 정리되지 않음 |
메모리 | 12.2MB | 44.8MB |
기능
실제 프로젝트에서 Kingfisher를 사용하게 된 것에는 기능들에 유용한 부분들이 많았습니다. 아래는 Kingfisher의 주요 기능들입니다.
- Asynchronous image downloading and caching.
- Loading image from either `URLSession`based networking or local provided data.
- Useful image processors and filters provided.
- Multiple-layer hybrid cache for both memory and disk.
- Fine control on cache behavior. Customizable expiration date and size limit.
- Cancelable downloading and auto-reusing previous downloaded content to improve performance.
- Independent components. Use the downloader, caching system, and image processors separately as you need.
- Prefetching images and showing them from the cache to boost your app.
- Extensions for `UIImageView`, `NSImageView`, `NSButton`, `UIButton`, `NSTextAttachment`, `WKInterfaceImage`, `TVMonogramView` and `CPListItem` to directly set an image from a URL.
- Built-in transition animation when setting images.
- Customizable placeholder and indicator while loading images.
- Extensible image processing and image format easily.
- Low Data Mode support.
- SwiftUI support.
캐싱을 제공하는 라이브러리이기 때문에 자동적으로 이미지를 불러오게 되면, 같은 요청에 대해서는 로컬에 저장되어 있는 이미지를 반환하게 됩니다. 또한, 캐싱의 방식이 메모리와 디스크를 모두 제공하고 hybrid 방식으로 활용하고 있기 때문에 용이하게 사용할 수 있습니다. 다양한 transition이나 이미지 실패시 나올 이미지 등 편리한 기능들을 제공하고 있으며, 실제 이미지를 저장하지 않고 이미지를 자른 후에 저장하는 등 많은 기능들을 제공하고 있습니다.
제가 실제 프로젝트에서 선택하게 된 이유는 Request에 있습니다. 실제 이미지를 받아오는 공간은 네트워크 요청에 대해서 Auth에 대한 정보를 제공하여야 하였고, 공통적으로 활용하는 Request에 대해서 수정을 할 수 있습니다. 다음은 실제 프로젝트에서 활용한 코드입니다.
let modifier = AnyModifier { request in
var request = request
request.setValue(Bundle.main.apiKey, forHTTPHeaderField: "apikey")
request.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
return request
}
KingfisherManager.shared.defaultOptions = [.requestModifier(modifier)]
실제 프로젝트 API에 대한 key값을 설정할 수 있었으며, 사용자 인증 정보를 추가하여 기본 Option으로 지정할 수 있습니다.
하지만, 이미지 변경에 대한 처리에 대해서 불편함을 느꼈습니다. 우리는 서버에서 이미지가 변경된 것을 어떻게 알 수 있을까요? 실제로 프로젝트에서 사용자의 프로필 사진이 변경 한 후 사용자의 이미지는 변환이 되어야 하는데 서버에 이미지를 업로드 한다고 하여도 Kingfisher는 캐싱이 되어 있는 상황에서는 데이터가 변경되지 않습니다.
ETag를 통해서 서버측 이미지가 변경된 것을 알 수 있습니다. 하지만, Kingfisher는 이에 대해서 지원하지 않았고, 저는 직접 캐시 저장소에 존재하는 데이터를 변경하는 방법 밖에 없었습니다.
ETag에 대해서 조금 더 설명을 하자면, Request를 보낼 때 이전에 가지고 있던 Image의 ETag 값과 현재 서버측에 존재하는 이미지의 ETag의 값을 비교하고 다를 경우에 데이터를 보내주는 것을 이야기합니다. 이것은 서버의 정책과 클라이언트 쪽 정책에 따라서 변경이 가능할 것 같습니다.
Nuke
성능
Nuke는 요즘 핫한 이미지 라이브러리이고, 매우 활발한 Community를 가지고 있습니다. Nuke는 이미지를 로드하는 과정에서 CPU 사용량은 최대 78% 증가하였고, 메모리는 90MB가 상승하였습니다.
구분 | 실행 전 | 실행 후 | 비고 |
CPU | 0% | 78% | 쓰레드가 정리되지 않음 |
메모리 | 12.1MB | 90MB |
Nuke 라이브러리는 공식 문서에서 퍼포먼스에 대한 가이드를 제공하고 있어서 참고하였습니다. Nuke 측에서는 메모리 캐싱과 HTTP 캐싱을 기본적으로 활용하고 있으며, 성능을 위해서 최적화에 대해서 많은 고민을 하고 해결하고 있는 모습이였습니다. 다음은 해당 아티클에서 설명하고 있는 성능적인 이점들입니다.
- 메모리 캐시
- 메모리 캐시는 LRU 알고리즘을 통해서 수행
- 백그라운드로 전환될 때 메모리 경고에 따라서 이미지를 삭제
- HTTP 캐시
- URLCache를 활용하여서 HTTP 응답에 대한 캐시를 수행
- 기본적으로 동작하게 됨
- 이를 통해서 이미지 데이터가 변경되었는지 확인할 수 있음
- 추가 사항
- Nuke는 메인 쓰레드에서 작업을 최소화하기 위해서 최적화를 사용하고 있음
- ImageRequest 가 주요한 이미지 작업의 요소 중 하나인데, 값타입으로 사용함.
- ImageRequest 내부에 존재하는 속성들을 optional set을 사용하여서 메모리 배치를 효율적으로 함
- 보통의 프레임워크는 캐싱을 위한 키로 String을 활용하지만, String은 무거운 객체이기 때문에 다른 타입을 활용함.
기능
Nuke에서 가장 재미있었던 기능은 기본적으로 Combine을 제공한다는 것이였습니다. PipeLine이라는 객체를 통해서 이미지를 가지고 오는 Nuke는 PipeLine 객체의 Publisher를 제공하여서 이미지 로드에 대해서 이벤트에 따라서 프로세싱하도록 하였습니다. 추가적인 기능은 Kingfisher나 다른 라이브러리들과 비슷합니다.
결론
저는 프로젝트에서 Kingfisher 라이브러리를 사용하게 된 이유는 많지만, 직접적으로는 직관적인 캐시의 구조에 있었습니다. 이미지의 변경 사항을 알 수 없고, 불편한 점들도 존재하지만, 성능적으로 더 좋았으며 이미지를 자르거나 꾸미는 부분에서 더욱 편하다고 생각이 들었습니다. 하지만, 이미지를 불러오는 과정에서 데이터가 변경되었는지를 알 수 있는 방법이 없는 것은 매우 불편하였습니다. 실제로는 모든 라이브러리들이 사용자의 눈에 거슬릴 정도의 시간을 보여주기 때문에 앱 내에서 이미지를 어떤 방식으로 보여줄 것이고, 서버측에서 이미지를 저장하는 방법과 요청에 응답을 주는 방식에 따라서 달라질 것입니다.
'IOS > UIKit' 카테고리의 다른 글
[iOS] MVVM에 대한 고찰 (0) | 2023.09.12 |
---|---|
[iOS] 상단 탭바 구성하기 (0) | 2023.09.11 |