[Apple] Asynchronously Loading Images into Table and Collection Views

J.Noma·2022년 2월 3일
0

Reference


예제 다운로드를 지원하는 문서입니다

🌀 Overview

이미지를 캐싱하는 것은 Table/CollectionView를 빠르게 초기화하고 스크롤링에 빠르게 응답할 수 있게 해줍니다. 예제 프로젝트 App은 URL을 사용하여 이미지를 로딩하도록 구현되어 있습니다. 이 이미지들은 asset 카탈로그가 아닌 번들에 포함되어 각각을 URL로 비동기적으로 로딩하는 것을 시뮬레이션합니다. 이는 UI의 반응성을 보장합니다. 예제 프로젝트는 또한 Mac Catalyst를 지원합니다

🌀 이미지 로딩/캐싱 다루기

예제 프로젝트에서 ImageCache.swiftURLSession을 통한 URL 기반 이미지 로딩과 NSCache를 통한 캐싱의 기본적인 메커니즘을 보여줍니다. 참고로 TableView/CollectionView는 ScrollView의 subclass입니다

예제 App에서 유저가 스크롤하면 동일한 이미지를 반복적으로 요청하게 됩니다. 예제에선 이미지 로딩이 완료될 때까지 completion block을 홀드하다가 이후 이미지를 모든 요청 block으로 전달합니다. 그래서 API는 주어진 URL에 대한 이미지를 fetch하기 위해 한 번의 호출만 하면 됩니다. 아래 코드는 예제 코드가 어떻게 기본적인 캐싱/로딩을 구현했는지 보여줍니다

// Returns the cached image if available, otherwise asynchronously loads and caches it.
final func load(url: NSURL, item: Item, completion: @escaping (Item, UIImage?) -> Swift.Void) {
    // Check for a cached image.
    if let cachedImage = image(url: url) {
        DispatchQueue.main.async {
            completion(item, cachedImage)
        }
        return
    }
    // In case there are more than one requestor for the image, we append their completion block.
    if loadingResponses[url] != nil {
        loadingResponses[url]?.append(completion)
        return
    } else {
        loadingResponses[url] = [completion]
    }
    // Go fetch the image.
    ImageURLProtocol.urlSession().dataTask(with: url as URL) { (data, response, error) in
        // Check for the error, then data and try to create the image.
        guard let responseData = data, let image = UIImage(data: responseData),
            let blocks = self.loadingResponses[url], error == nil else {
            DispatchQueue.main.async {
                completion(item, nil)
            }
            return
        }
        // Cache the image.
        self.cachedImages.setObject(image, forKey: url, cost: responseData.count)
        // Iterate over each requestor for the image and pass it back.
        for block in blocks {
            DispatchQueue.main.async {
                block(item, image)
            }
            return
        }
    }.resume()
}

🌀 책임감있게 DataSource 업데이트하기

런치 시점에 모든 이미지를 로드하는 것은 메모리가 부족하거나 완료까지 너무 오래 걸려 종료될 위험이 있습니다. App이 당장 모든 데이터를 필요로 하는게 아니라면 UI가 해당 이미지를 요청하는 시점에 로드하는게 좋습니다

📌 NOTE
화면에 보여지기 전에 item 로딩을 보장하기 위해 prefetching API를 사용할 수 있습니다. Prefetching Collection View Data를 참고합니다

일반적으로 App은 data source가 cell에게 image를 가져와 세팅할 때까지 기다립니다. 예제 코드에선 이미지를 fetching하여 재사용 view에 display하는 한가지 접근법 예시를 보여줍니다

var content = cell.defaultContentConfiguration()
content.image = item.image
ImageCache.publicCache.load(url: item.url as NSURL, item: item) { (fetchedItem, image) in
    if let img = image, img != fetchedItem.image {
        var updatedSnapshot = self.dataSource.snapshot()
        if let datasourceIndex = updatedSnapshot.indexOfItem(fetchedItem) {
            let item = self.imageObjects[datasourceIndex]
            item.image = img
            updatedSnapshot.reloadItems([item])
            self.dataSource.apply(updatedSnapshot, animatingDifferences: true)
        }
    }
}
cell.contentConfiguration = content
profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

0개의 댓글