일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 반응형
- mvc
- WeatherKit
- CS
- 학과별커뮤니티
- struct
- 캐시
- 대표
- 토이프로젝트
- SwiftUI
- async
- swift
- 네트워크
- 옵셔널
- ios
- MVVM
- 기초문법
- 이론
- RxSwift
- collectionview
- 라이브러리
- 실습
- uikit
- 동시성
- 스트럭트
- snkit
- 구름톤 유니브
- Kingfisher
- 세종대학교
- 프로토콜
- Today
- Total
스윞한 개발자
SNKit #5: 이미지 처리, SNKit의 마무리... 본문
안녕하세요! 이번 포스팅에서는 SNKit 시리즈의 마지막 이야기를 적어보려 합니다.

지금까지 SNKit의 개발 배경, 캐시 시스템, ETag 검증 및 다운로드 시스템에 대해 살펴보았습니다. 이번 글에서는 SNKit의 마지막 핵심 기능인 이미지 처리 시스템과 UIKit과의 통합(추후에 SwiftUI도 반영예정입니다!)에 대해 자세히 알아보겠습니다.
이미지 처리의 중요성
모바일 앱에서 이미지 처리는 성능과 사용자 경험에 큰 영향을 미칩니다.
1. 메모리 효율성
고해상도 이미지를 그대로 사용하면 메모리 사용량이 급증하여 앱 성능이 저하되고, 최악의 경우 메모리 부족으로 인한 앱 종료가 발생할 수 있습니다. 특히 iOS 기기에서는 메모리 한계가 명확하기 때문에, 이미지 크기를 적절히 조절하는 것이 필수적입니다.
2. 렌더링 성능
이미지 크기가 실제 표시 영역보다 훨씬 크면 불필요한 렌더링 작업이 발생하여 UI 성능이 저하됩니다. 표시 영역에 맞게 이미지를 처리하면 렌더링 성능을 크게 향상할 수 있습니다.
3. 네트워크 효율성
특히 사용자 환경에 맞게 최적화된 이미지를 제공하는 서버가 없는 경우, 클라이언트 측에서 이미지 처리가 필요합니다. 원본 이미지를 다운로드한 후 앱에서 필요한 크기로 처리하면 사용자 경험을 향상할 수 있습니다.
이미지 처리 옵션 설계
SNKit에서는 다양한 이미지 처리 요구사항을 충족시키기 위해 ImageProcessingOption 열거형을 정의했습니다.
public enum ImageProcessingOption: Equatable {
case resize(CGSize) // 이미지 리사이징
case downsample(CGSize) // 이미지 다운샘플링
case none // 처리 없음
}
resize
표준적인 이미지 리사이징 방식으로, UIKit의 이미지 렌더링 시스템을 사용합니다. 빠르고 쉽게 이미지 크기를 조정할 수 있지만, 메모리 사용량이 상대적으로 높을 수 있습니다.
downsample
메모리 효율적인 다운샘플링 방식으로, 원본 이미지 데이터에서 직접 필요한 크기만 디코딩합니다. 이 방식은 메모리 사용량을 크게 줄일 수 있지만, 처리 시간이 조금 더 걸릴 수 있습니다.
none
이미지 처리를 하지 않고 원본 그대로 사용합니다.
이미지 프로세서 구현
final class ImageProcessor {
func process(_ image: UIImage, with option: ImageProcessingOption) -> UIImage? {
switch option {
case .resize(let size):
return resizeImage(image, to: size)
case .downsample(let size):
return downsampleImage(image, to: size)
case .none:
return image
}
}
private func resizeImage(_ image: UIImage, to size: CGSize) -> UIImage? {
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { context in
image.draw(in: CGRect(origin: .zero, size: size))
}
}
private func downsampleImage(_ image: UIImage, to size: CGSize) -> UIImage? {
guard let data = image.jpegData(compressionQuality: 1.0),
let source = CGImageSourceCreateWithData(data as CFData, nil) else { return nil }
let options: [CFString: Any] = [
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height)
]
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else { return nil }
return UIImage(cgImage: cgImage)
}
}
리사이징 구현
resizeImage 메서드는 UIKit의 UIGraphicsImageRenderer를 사용하여 이미지를 리사이징합니다. 이 방식은 간단하지만, 내부적으로 이미지 전체를 메모리에 로드한 후 처리하기 때문에 메모리 사용량이 높을 수 있습니다.
private func resizeImage(_ image: UIImage, to size: CGSize) -> UIImage? {
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { context in
image.draw(in: CGRect(origin: .zero, size: size))
}
}
다운샘플링 구현
downsampleImage 메서드는 Core Graphics의 이미지 소스 API를 사용하여 이미지를 다운샘플링합니다. 이 방식은 원본 이미지 데이터에서 필요한 크기만 디코딩하여 메모리 사용량을 크게 줄일 수 있습니다.
private func downsampleImage(_ image: UIImage, to size: CGSize) -> UIImage? {
guard let data = image.jpegData(compressionQuality: 1.0),
let source = CGImageSourceCreateWithData(data as CFData, nil) else { return nil }
let options: [CFString: Any] = [
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height)
]
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else { return nil }
return UIImage(cgImage: cgImage)
}
- kCGImageSourceCreateThumbnailWithTransform: 이미지의 방향 정보를 유지합니다.
- kCGImageSourceCreateThumbnailFromImageAlways: 항상 썸네일을 생성합니다.
- kCGImageSourceThumbnailMaxPixelSize: 생성될 썸네일의 최대 픽셀 크기를 지정합니다.
이러한 옵션을 통해 원본 이미지의 전체 해상도를 로드하지 않고도 필요한 크기의 이미지를 효율적으로 생성할 수 있습니다.
UIImageView 확장 구현
SNKit을 개발자가 편하게 쓸수 있도로 만들기 위해 UIImageView 클래스를 확장하여 쉽게 이미지를 로드할 수 있는 방법을 제공했습니다.
public extension UIImageView {
func snSetImage(
with url: URL,
cacheOption: CacheOption = .cacheFirst,
storageOption: StorageOption? = nil,
processingOption: ImageProcessingOption = .none,
completion: ((Result<UIImage,Error>) -> Void)? = nil
) {
let storageOpt = storageOption ?? SNKit.shared.defaultStorageOption
if let cacheImage = SNKit.shared.cachedImage(for: url),
cacheOption == .cacheFirst {
if processingOption != .none {
DispatchQueue.global(qos: .userInitiated).async {
let imageProcessor = ImageProcessor()
let processedImage = imageProcessor.process(cacheImage, with: processingOption) ?? cacheImage
DispatchQueue.main.async {
self.image = processedImage
completion?(.success(processedImage))
}
}
} else {
self.image = cacheImage
completion?(.success(cacheImage))
}
return
}
SNKit.shared.loadImage(
from: url,
cacheOption: cacheOption,
storageOption: storageOption,
processingOption: processingOption
) { [weak self] result in
switch result {
case .success(let image):
self?.image = image
completion?(.success(image))
case .failure(let error):
completion?(.failure(error))
}
}
}
}
- 간편한 API: 한 줄의 코드로 이미지 로드, 캐싱, 처리(리사이징, 다운샘플링)를 모두 처리할 수 있습니다.
- 캐시 최적화: 캐시된 이미지가 있는 경우 즉시 사용하고, 필요한 처리를 백그라운드에서 수행합니다.
- 커스터마이징 옵션: 캐싱 옵션, 저장 옵션, 처리 옵션 등 다양한 매개변수를 통해 세밀한 제어가 가능합니다.
- 완료 콜백: 비동기 작업이 완료되면 결과를 콜백으로 전달받을 수 있습니다.
- 메모리 관리: 약한 참조(weak self)를 사용하여 메모리 누수를 방지합니다.
설정 시스템
public struct Configuration {
public let memoryCacheCapacity: Int
public let diskCacheCapacity: Int
public let expirationPolicy: ExpirationPolicy
public let cacheDirectory: URL?
public let defaultStorageOption: StorageOption
public init(
memoryCacheCapacity: Int = 50_000_000,
diskCacheCapacity: Int = 100_000_000,
expirationPolicy: ExpirationPolicy = ExpirationPolicy(rule: .days(7)),
cacheDirectory: URL? = nil,
defaultStorageOption: StorageOption = .hybrid
) {
self.memoryCacheCapacity = memoryCacheCapacity
self.diskCacheCapacity = diskCacheCapacity
self.expirationPolicy = expirationPolicy
self.cacheDirectory = cacheDirectory
self.defaultStorageOption = defaultStorageOption
}
}
이 구성을 통해 다음과 같은 설정을 조정할 수 있습니다.
- 메모리 캐시 용량: 메모리 캐시가 사용할 최대 메모리 크기 (기본값: 50MB)
- 디스크 캐시 용량: 디스크 캐시가 사용할 최대 디스크 공간 (기본값: 100MB)
- 만료 정책: 캐시 항목의 기본 만료 정책 (기본값: 7일)
- 캐시 디렉토리: 디스크 캐시 파일이 저장될 사용자 정의 디렉토리
- 기본 저장 옵션: 기본 캐시 저장 방식 (기본값: 하이브리드)
let configuration = Configuration(
memoryCacheCapacity: 100_000_000, // 100MB
diskCacheCapacity: 500_000_000, // 500MB
expirationPolicy: .days(30), // 30일 후 만료
defaultStorageOption: .disk // 디스크 저장 우선
)
let snkit = SNKit(configuration: configuration)
이렇게 생성된 인스턴스는 사용자 정의 설정에 따라 동작하며, 앱의 특성이나 요구사항에 맞게 최적화할 수 있습니다.
시리즈를 마치며
지금까지 4편에 걸쳐 SNKit 이미지 캐싱 라이브러리의 개발 과정과 주요 기능에 대해 개발일지를 작성해 보았습니다.
SNKit은 효율적인 메모리 및 디스크 캐싱, 다양한 이미지 처리 옵션, 그리고 사용하기 쉬운 API를 통해 iOS 앱의 이미지 로딩 성능을 크게 향상할 수 있습니다.
SNKit은 오픈 소스 프로젝트로, 계속해서 SwiftUI... 와 더불어 Kingfisher에서 사용되는 다양한 옵션을 더 추가하지 않을까.. 싶습니다!!
잘못된 부분에 대한 피드백은 언제나 환영입니다. 긴 글 읽어주셔서 감사합니다!
https://github.com/jeoungsung12/SNKit
GitHub - jeoungsung12/SNKit
Contribute to jeoungsung12/SNKit development by creating an account on GitHub.
github.com

'프로젝트' 카테고리의 다른 글
SNKit #4: ETag 검증과 이미지 다운로드 시스템 (2) | 2025.04.08 |
---|---|
SNKit #3: 디스크/하이브리드 캐시의 구현 (0) | 2025.03.31 |
SNKit #2: 메모리 캐시의 구현 (0) | 2025.03.25 |
SNKit #1: iOS 이미지 캐싱 라이브러리 기획 (0) | 2025.03.16 |
그래빗 : 성공적인 투자 GraBit과 함께! (0) | 2024.04.08 |