Swift에서 비동기 이미지 처리를 위해 가장 먼저 떠오르고 많이 사용되는 라이브러리는 KingFisher 입니다. 오랫동안 많은 iOS 프로젝트에서 사용되어 왔고, 사용법도 간단하고, 자료가 많아 이제는 비동기 이미지 처리에선 대표적인 라이브러리라고 할 수 있습니다.
하지만 최근 Nuke 라는 라이브러리가 눈에 들어왔고, KingFisher보다 퍼포먼스가 좋다는 이미지를 보고 궁금해졌습니다. 그래서 한번 어떤 장점이 있는지 알아보려고 합니다.
Nuke 가 제공하는 기능들
- URL 기반 이미지 비동기 로딩
- 메모리, 디스크 캐싱 지원
- 플레이스홀더, 에러 처리
- 리사이징, 라운딩 등 이미지 후 처리
- SwiftUI, UIKit 지원
- 다양한 이미지 포맷 지원(jpeg, png, svg, heif, gif, webp)
이미지 처리 라이브러리에게 기대하는 기능들 대부분을 갖추고 있으며, KingFisher에서 제공하는 대부분의 기능들이 구현되어 있습니다.
사용 방법
ImagePipeline은 이미지 다운로드, 캐시 처리, 디코딩, 표시 준비 등 Nuke의 전체 흐름을 담당하는 핵심 요소 입니다.
이미지를 불러오려면 간단하게는 아래처럼 사용할 수 있습니다.
let image = try await ImagePipeline.shared.image(for: url)
만약 이미지 다운로드의 진행 상태를 보고 싶거나, 로딩 중 상태에 따라 뷰를 업데이트하려면 ImagePipeline.shared.imageTask(with:) 를 사용할 수 있습니다.
func loadImage() async throws {
let imageTask = ImagePipeline.shared.imageTask(with: url)
for await progress in imageTask.progress {
print("Progress: \(progress.completed) / \(progress.total ?? 0)")
}
imageView.image = try await imageTask.image
}
ImagePipeline.shared를 사용해 기본 옵션으로 사용할 수 있고, 캐시 정책 등 커스텀도 가능합니다.
ImagePipeline {
$0.dataCache = try? DataCache(name: "com.myapp.datacache")
$0.dataCachePolicy = .automatic
}
Nuke의 장점
학습 비용이 낮음
Nuke는 공식 문서를 제공합니다.
서드파티 라이브러리를 도입할 때 가장 우려되는 부분이 유지보수인데, 유지보수를 하기 위해선 해당 라이브러리에 대한 이해가 반드시 필요합니다. 그러나 대부분의 라이브러리가 리드미를 통해 간단한 사용법 정도만 설명하고 있어, 충분한 이해를 위해선 머리아픈 코드 분석이 뒷받침됩니다.
하지만 Nuke는 공식 문서가 잘 되어있고, Demo App까지 제공해주어 빠르게 학습이 가능합니다.
Nuke
https://kean-docs.github.io/nuke/documentation/nuke/getting-started
NukeUI
https://kean-docs.github.io/nukeui/documentation/nukeui/
NukeExtensions
https://kean-docs.github.io/nukeextensions/documentation/nukeextensions/
Nuke Demo App
https://github.com/kean/NukeDemo
성능 최적화가 잘 되어있음
공식 문서의 Performance Guide를 보면 아래와 같이 설명합니다.
3 Layer 캐시 시스템
Nuke는 3 Layer의 이미지 캐싱 계층을 가집니다.
L1. Memory Cache(Default)
디코딩된 이미지를 인메모리(In-Memory)에 저장합니다.
기본적으로 활성화되어 있으며, LRU(Least Recently Used) 알고리즘을 사용합니다.
앱이 백그라운드로 전환될 때 캐시를 제거해 메모리를 효율적으로 사용합니다.
L2. HTTP Disk Cache(Default)
메모리에 캐시되지 않은 이미지는 URLCache에 저장합니다.
Cache-Control을 통해 서버에서 캐싱 여부를 결정할 수 있습니다.
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
Expires: Mon, 26 Jan 2016 17:45:57 GMT
Last-Modified: Mon, 12 Jan 2016 17:45:57 GMT
ETag: "686897696a7c876b7e"
L3. Aggressive Disk Cache(Optional)
서버에서 Cache-Control을 사용하지 않고, 이미지가 고정된 URL을 가진다면 L2 대신 사용 가능합니다.
논블로킹 비동기 write가 가능해 빠른 캐싱을 제공합니다.
기본적으론 원본 이미지 데이터를 저장하지만, 옵션을 통해 처리된 이미지를 저장할 수 있습니다.
Prefetching
프리 패칭은 해당 이미지가 사용되기 전에 미리 데이터를 다운로드하는 것을 의미합니다.
이미지를 다운로드하고 디코당하는 작업은 CPU와 메모리에 부하를 주기 때문에, ImagePrefetcher(destination: .diskCache)를 사용하여 이미지 디코딩을 스킵하고 디스크 캐시에 저장해둘 수 있습니다.
final class PrefetchingDemoViewController: UICollectionViewController {
private let prefetcher = ImagePrefetcher()
private var photos: [URL] = []
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.isPrefetchingEnabled = true
collectionView?.prefetchDataSource = self
}
}
extension PrefetchingDemoViewController: UICollectionViewDataSourcePrefetching {
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
let urls = indexPaths.map { photos[$0.row] }
prefetcher.startPrefetching(with: urls)
}
func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
let urls = indexPaths.map { photos[$0.row] }
prefetcher.stopPrefetching(with: urls)
}
}
Decompressing(디컴프레션)
다운로드된 이미지는 데이터 형태로, 화면에 보여지려면 디코딩 과정이 필요합니다.
Nuke는 이미지 디코딩과 압축을 백그라운드 스레드에서 수행하여 메인 스레드 블로킹을 방지합니다.
Coalescing (중복 요청 제거)
같은 이미지 URL을 동시에 요청하면, 하나의 요청으로 병합하고 재사용합니다.
리사이징, 블러 효과 등 각 뷰에서 중복된 이미지 처리를 요청할 경우 한번만 수행한 다음 재사용합니다.
let url = URL(string: "http://example.com/image")
pipeline.loadImage(with: ImageRequest(url: url, processors: [
.resize(size: CGSize(width: 44, height: 44)),
.gaussianBlur(radius: 8)
]))
pipeline.loadImage(with: ImageRequest(url: url, processors: [
.resize(size: CGSize(width: 44, height: 44))
]))
Progressive Decoding
느린 네트워크 환경에서 이미지를 점점 선명하게 렌더링합니다.
먼저 전체 이미지의 흐릿한 버전이 표시되며, 전체 다운로드가 끝나면 완전한 선명한 이미지를 보여줍니다.
플레이스 홀더를 통해 이미지를 불러오고 있음을 알려주는 것 이상의 사용자 경험을 줄 수 있습니다.
기본 | Progressive |
---|---|
![]() |
![]() |
Main Thread Perfomance
Nuke는 아래와 같은 방식으로 메인 스레드 부하를 최소화합니다.
CoW(Copy on Write)
ImageRequest는 Nuke의 핵심적인 타입으로, 여러 옵션을 포함하고 있어 구조체의 크기가 큰 편.
복사 비용을 줄이기 위해 CoW 기법을 적용해 효율적으로 전달합니다.
OptionSet
ImageRequest의 다양한 옵션들을 OptionSet 타입으로 통합하고, 구조체 내 프로퍼티 순서를 조정해서 메모리 stride(간격)을 최적화합니다. 그 결과 메모리 사용량이 176 byte -> 48 byte 로 줄었습니다.
ImageRequest.CacheKey
대부분의 이미지 라이브러리는 고유 값을 구분하기 위해 문자열을 사용하지만, Nuke는 ImageRequest.CacheKey라는 내부 타입을 사용해 비교합니다. 그 결과, 캐시 조회 및 비교 시 연산 속도를 높였습니다.
let request = ImageRequest(
url: URL(string: "http://example.com/image.jpeg"),
processors: [.resize(width: 320)],
priority: .high,
options: [.reloadIgnoringCachedData]
)
let image = try await pipeline.image(for: request)
마치며
여기까지 Nuke 공식 문서를 기반으로 Nuke가 제공하는 기능들과 성능적인 장점들을 살펴보았습니다.
KingFisher가 Best Practice처럼 널리 사용되고 있는 건 사실이지만, Nuke는 Swift Concurrency 등 최신 문법을 잘 지원하고 있고, 많은 커스터마이징 요소, 성능까지 신경 쓴 게 문서를 통해 알 수 있어 흥미로웠습니다.
아직까지는 커뮤니티 자료나 사용 사례들이 KingFisher보다 부족하지만, 공식 문서와 샘플이 잘 준비되어 있어서 도입하는 데 큰 어려움은 없어 보입니다.
비동기 이미지 처리의 비중이 큰 프로젝트거나, async/await 기반 비동기 흐름을 중요하게 생각한다면 KingFisher 대신 고민해볼 여지가 있을 것 같습니다.
'iOS' 카테고리의 다른 글
Shorts UI 구현하기 (SwiftUI+UIViewRepresentable+UICollectionView) (2) | 2025.07.20 |
---|---|
WWDC25 - FoundationModels로 엿본 애플이 꿈꾸는 개인화 AI의 미래 (1) | 2025.06.24 |
iOS Hang, Hitch 그리고 Render Loop (0) | 2025.06.16 |
의존성 역전을 통한 독립적인 네트워크 모듈 설계하기 (2) | 2025.06.15 |
Demystify SwiftUI - Identify: SwiftUI는 뷰를 어떻게 구분할까? (0) | 2025.06.09 |