Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- rxswift
- uitableview
- 리펙토링
- Observable
- swiftUI
- Clean Code
- Refactoring
- RxCocoa
- combine
- Xcode
- Human interface guide
- map
- uiscrollview
- HIG
- swift documentation
- UICollectionView
- SWIFT
- 리펙터링
- 클린 코드
- 스위프트
- MVVM
- ribs
- UITextView
- tableView
- Protocol
- collectionview
- clean architecture
- 애니메이션
- ios
- 리팩토링
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 2. 이미지 캐싱(imageCache), 이미지 효율적으로 로드 방법(tableView, collectionView): async + NSCache, pagination 본문
iOS 응용 (swift)
[iOS - swift] 2. 이미지 캐싱(imageCache), 이미지 효율적으로 로드 방법(tableView, collectionView): async + NSCache, pagination
jake-kim 2021. 10. 2. 23:30
1. 이미지 캐싱, 이미지 효율적으로 로드 방법, 스크롤에 따라 이미지 로드(tableView, collectionView): scrollViewDidScroll, prefetch
2. 이미지 캐싱, 이미지 효율적으로 로드 방법(tableView, collectionView): async + NSCache
cf) 애플 공식 문서에서 나온 ImageCache 방법은 여기 참고
처리 방법 2단계
- scrollViewDidScroll, prefetch 방법으로 모든 페이지의 이미지를 한꺼번에 호출하지 않고 스크롤에 따라 page를 늘려나가며 API호출 > 이미지 url들을 획득
- 이미지 url들만 우선 cell의 모델에 적용
- ImageCache를 통해서 url들에 대해서 이미지 로드 > cell의 item 모델 객체에 image데이터 입력 > Diffable Data Source방법을 통해 셀 업데이트
ImageCache 모듈
1) 이미지 캐시 모듈과 cell에 사용될 protocol 정의
- Diffable Data Source 방법을 사용해야 하므로 Hashable할 수 있도록 identifier프로퍼티도 선언
- <SectionIdentifier, ItemIdentifer>에서 ItemIdentifer에 들어갈 모델
// ImageCache.swift
protocol Item {
var image: UIImage { get set }
var imageUrl: URL { get }
var identifier: UUID { get }
}
- ImageCompletion 정의: ImageCache의 결과값은 completion으로 사용
- (Item, UIIMage?) -> void 에서 Item은 기존 Item이고 UIImage는 새로 얻어온 image데이터를 의미
- 사용하는 쪽에서 저 결과값을 받아서, item.image와 UIImage를 비교하여 동일하다면 업데이트하지 않게끔 사용
// ImageCache.swift
typealias ImageCompletion = (Item, UIImage?) -> Void
- imageCache 클래스 정의
- prefetch image: viewController에 있는 prefetchRowAt 델리게이트 메소드에서 불릴 기능
- load image: cache에 존재하면 그 데이터를 사용하고, 없으면 네트워크 호출을 실행
// ImageCache.swift
class ImageCache {
let provider: Provider
init(provider: Provider) {
self.provider = provider
}
private let cache = NSCache<NSURL, UIImage>()
private var prefetches: [UUID] = []
private var completions: [NSURL: [ImageCompletion]] = [:]
// Prefetch
func prefetchImage(for item: Item) {
let url = item.imageUrl as NSURL
guard cachedImage(for: url) == nil, !prefetches.contains(item.identifier) else { return }
prefetches.append(item.identifier)
provider.request(item.imageUrl) { [weak self] result in
switch result {
case .success(let data):
guard let image = UIImage(data: data) else { return }
self?.cache.setObject(image, forKey: url)
self?.prefetches.removeAll { $0 == item.identifier }
default: break
}
}
}
// Load
func loadImage(for item: Item, completion: @escaping ImageCompletion) {
let url = item.imageUrl as NSURL
if let image = cachedImage(for: url) {
completion(item, image)
return
}
if !completions.isEmpty, completions[url] != nil {
completions[url]?.append(completion)
return
} else {
completions[url] = [completion]
}
provider.request(item.imageUrl) { [weak self] result in
switch result {
case .success(let data):
guard let image = UIImage(data: data) else { return }
guard let completions = self?.completions[url] else {
completion(item, nil)
return
}
self?.cache.setObject(image, forKey: url)
completions.forEach { completion in
completion(item, image)
}
case .failure(let error):
print(error)
completion(item, nil)
}
self?.completions.removeValue(forKey: url)
}
}
// Reset
func reset() {
completions.removeAll()
prefetches.removeAll()
cache.removeAllObjects()
}
// Cache
private func cachedImage(for url: NSURL) -> UIImage? {
cache.object(forKey: url)
}
}
ViewModel에서 사용
- prefetch (ViewController의 prefetchAtRow 델리게이트 메소드에서 불리는 메소드)
// PhotoViewModel.swift
func prefetchImage(at indexPath: IndexPath) {
guard let photo = dataSource.itemIdentifier(for: indexPath) else {
return
}
imageCache.prefetchImage(for: photo)
}
- 이미지 로드 (ViewController에서 UITableViewDiffableDataSource 클로저 블록에서 불리는 메소드)
// PhotoViewModel.swift
func loadImages(for photo: Photo) {
imageCache.loadImage(for: photo) { [weak self] item, image in
guard let `self` = self else { return }
guard let photo = item as? Photo else { return }
guard let image = image, image != photo.image else { return }
photo.image = image
var snapshot = `self`.dataSource.snapshot()
guard snapshot.indexOfItem(photo) != nil else { return }
snapshot.reloadItems([photo])
DispatchQueue.global(qos: .background).async {
`self`.dataSource.apply(snapshot, animatingDifferences: false)
}
}
}
* 전체 소스코드: https://github.com/JK0369/PaginationExample
* 참고
- preFetch tableView: https://andreygordeev.com/2017/02/20/uitableview-prefetching/
- preFetch collectionView: https://andreygordeev.com/2017/02/20/uitableview-prefetching/
-Asynchronously Loading Images into Table and Collection Views: https://developer.apple.com/documentation/uikit/views_and_controls/table_views/asynchronously_loading_images_into_table_and_collection_views
'iOS 응용 (swift)' 카테고리의 다른 글
Comments