Image Loader를 만들고 안에서 캐시를 처리하여 효율적으로 처리하는 기능을 만든다.
BUG 제거
지난번에 한 번만 부르면 더 안 불러졌었다.
이유는 간단한데 fetchAdditionalCharacters에서 isLoadingMoreCharacter를 다시 false로 바꿔주는 작업을 우리가 이전에 주석처리하고 진행했기 때문이다.
final class RMCharacterListViewViewModel: NSObject {
...
public func fetchAdditionalCharacters(url: URL) {
...
RMService.shared.execute(request,
expecting: RMGetAllCharacterResponse.self) { [weak self] result in
...
switch result {
case .success(let responseModel):
...
DispatchQueue.main.async {
strongSelf.delegate?.didLoadMoreCharacters(
with: indexPathsAdd)
strongSelf.isLoadingMoreCharacters = false
}
...
}
}
}
...
}
아주 잘 출력되는 걸 볼 수 있다.
이제는 이미지를 로딩하는 Image Loader를 만들 것이며, 이미지를 캐싱하여 보관할 것이다.
ImageLoader
Manager 그룹에서 RMImageLoader라는 파일을 만들자. 이번에도 싱글톤으로 사용할 것이다.
import Foundation
final class RMImageLoader {
static let shared = RMImageLoader()
private init() {}
func downloadImage(_ url: URL) {}
}
downloadImage 안에 여러 가지를 채워 넣어야 하는데 우리가 예전에 만들어 놨던 RMCharacterCollectionViewCellViewModel의 fetchImage에서 가져오자.
completion으로 받아야 하는 걸 잊으면 안 된다.
final class RMImageLoader {
static let shared = RMImageLoader()
private init() {}
public func downloadImage(_ url: URL, completion: @escaping (Result <Data, Error>) -> Void) {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data, error == nil else {
completion(.failure(error ?? URLError(.badServerResponse)))
return
}
completion(.success(data))
}
task.resume()
}
}
원래 가져왔던 곳에서는 싱글톤을 이용하여 메서드로 호출을 하자. 간단한 작업이지만 코드의 재사용성이 눈에 띄게 증가했다.
public func fetchImage(completion: @escaping (Result <Data, Error>) -> Void) {
guard let url = characterImageUrl else {
completion(.failure(URLError(.badURL)))
return
}
RMImageLoader.shared.downloadImage(url, completion: completion)
}
Cache
컬렉션뷰에서 만약 스크롤해서 아래로 내려가게 되면 화면에서 사라진 셀은 디큐 된다. 그래서 다시 위로 올라가면 사실 다시 다운로드하는 것이다.
- dequeue - 여러 가지 상황에서 쓸 수 있으나 이 경우 기존에 생성된 셀을 새로운 셀로 업데이트하고 재사용큐에 반환하는걸 cell dequeue라고 한다.
Cache를 사용하여 데이터를 저장해 놓고 사용해 보자. 우린 NSCache를 사용할 것이다. NSCache는 자동적으로 메모리 관리 시 필요한 만큼 저장해 놓기 때문에 성능에 큰 이슈가 없다는 좋은 점이 있다.
NSCache는 정의된 곳을 들어가면 KeyType과 ObjectType으로 선언해야 하는 것을 알 수 있다. 이를 참고하여 캐시를 만들고 만약 이미지가 캐시 안에 존재하면 API 통신을 하지 않고 꺼내다 쓰는 것으로 바꿔보자
imageDataCache라는 stored Propety를 NSCache로 선언한다. NSCache는 클래스만을 받을 수 있기 때문에 NSString, NSData로 선언해 주자.
그리고 task에서 이미지를 받아 올 때 imageDataCache에 저장한다. 잘 보면 setObject로 하고 있다. 리스트가 아니기 때문이다. 그리고 메서드 초반에 key(=이미지 주소)에 해당하는 데이터가 있다면 API를 사용하지 않고 바로 넣어주자
final class RMImageLoader {
private var imageDataCache = NSCache<NSString, NSData>()
...
public func downloadImage(_ url: URL, completion: @escaping (Result <Data, Error>) -> Void) {
let key = url.absoluteString as NSString
if let data = imageDataCache.object(forKey: key) {
completion(.success(data as Data))
return
}
...
let task = URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
...
let key = url.absoluteString as NSString
let value = data as NSData
self?.imageDataCache.setObject(value, forKey: key)
completion(.success(data))
}
task.resume()
}
}
그런데 어딘가에 버그가 있다. 스크롤을 계속 내리다 보면 터진다. 잠깐 언뜻 본 바로는 cell의 숫자가 안 맞아서 생기는 경우인 듯하다.
끝
'Swift > Rick & Morty' 카테고리의 다른 글
[iOS] Rick&Morty - #14 Compositional Layout (1) | 2023.03.12 |
---|---|
[iOS] Rick&Morty - #13 Character Detail View(2) (0) | 2023.03.11 |
[iOS] Rick&Morty - #11 Pagination (0) | 2023.03.08 |
[iOS] Rick&Morty - #10 Pagination Indicator (0) | 2023.03.07 |
[iOS] Rick&Morty - #9 Character Detail View (0) | 2023.03.06 |