Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

asnyc/await 사용하여 이미지의 thumbnail 비동기적으로 만들기 #12

Open
hyun99999 opened this issue Jun 29, 2022 · 0 comments
Assignees
Labels
documentation Improvements or additions to documentation

Comments

@hyun99999
Copy link
Member

  • iOS 15 부터 적용이 가능한 prepareThumbnail(of:completionHandler:) 메서드를 사용해서 동기적 코드에서 background 스레드에서 비동기적으로 thumbnail image 를 만드는 것을 해보자!
  • asnyc 로 선언된 비동기적 메서드인 byPreparingThumbnail(ofSize:) 를 사용해보자!
  • debug navigator 로 CPU, Memory 에 실제로 유효한지 확인해보자!

Meet async/await in Swift - WWDC21 - Videos - Apple Developer

WWDC 21 세션을 보다가 비동기적으로 thumbnail image 를 만드는 메서드가 보여서 적용해보기로 하였다.

먼저 개발자 문서를 확인해보자.

prepareThumbnail(of:completionHandler:)

Creates a thumbnail image at the specified size asynchronously on a background thread.

1

Discussion

Concurrency Note

completion handler 를 사용하여 동기 코드에서 이 메서드를 호출하거나 다음 선언이 있는 비동기 메서드로 호출할 수 있습니다.

func byPreparingThumbnail(ofSize size: CGSize) async -> UIImage?

UIImageView 에 이미지를 표시할 때, view 의 contentMode 프로퍼티를 사용하여 이미지를 자동으로 자르거나 크기를 조정할 수 있습니다. 그러나 기본 이미지 크기가 뷰의 bounds 보다 훨씬 큰 경우 전체 크기 이미지를 디코딩하면 불필요한 메모리 오버헤드가 생성됩니다. 이 방법을 사용하여 지정된 크기로 thumbnail 이미지를 생성하면 전체 크기로 이미지를 디코딩하는 오버헤드를 피할 수 있습니다.

이 메서드는 백그라운드 스레드에서 thumbnail 이미지를 비동기적으로 생성하고 해당 스레드에서 completion handler 를 호출합니다. 앱이 completion handler 에서 UI 를 업데이트하는 경우에는 main thread 에서 UI 업데이트를 예약해야 합니다.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? ItemCell else {
        fatalError("Unexpected type for cell. Check configuration.")
    }
        
    let item = items[indexPath.item]
    cell.nameLabel?.text = item.name
    item.image.prepareThumbnail(of: thumbnailSize) { thumbnail in
        DispatchQueue.main.async {
            cell.thumbnailImageView?.image = thumbnail
        }
    }
    return cell
}

byPreparingThumbnail(ofSize:)


해당 메서드는 개발자 문서에 별도로 페이지가 존재하지 않고, 위의 개발자 문서에서만 확인할 수 있었습니다. 동일한 기능이지만 asnyc context 에서 사용할 수 있는 점이 달랐습니다.

async 메서드인 byPreparingThumbnail(ofSize:) 를 사용해 보겠습니다.

Meet async/await in Swift - WWDC21 - Videos - Apple Developer

위의 세션의 코드를 참고하여 진행하겠습니다.

import UIKit

 extension UIImage {
     var thumbnail: UIImage? {
         get async {
             let size = CGSize(width: 100, height: 140)
             return await self.byPreparingThumbnail(ofSize: size)
         }
     }
 }

// 사용
guard let thumbnailImage = await cache[url]?.thumbnail else { throw ImageDownloadError.unsupportImage }

read-only properties 는 asnyc 가 가능합니다.

import UIKit

actor ImageDownloader {
    static let shared = ImageDownloader()
    private init() { }
    
    private var cache: [URL: UIImage] = [:]
    
    func image(from urlPath: String) async throws -> UIImage? {
        guard let url = URL(string: Const.Path.imageURLPath + urlPath) else {
            throw ImageDownloadError.invalidURLString(Const.Path.imageURLPath + urlPath)
        }
        
        if let cached = cache[url] {
            return cached
        }
        
        let image = try await downloadImage(from: url)
        
        cache[url] = cache[url, default: image]
        
        // 🔥 캐싱된 이미지의 thumbnail 을 만들어서 반환.
        guard let thumbnailImage = await cache[url]?.thumbnail else { throw ImageDownloadError.unsupportImage }
        
        return thumbnailImage
    }

    private func downloadImage(from url: URL) async throws -> UIImage {
        let imageFetchProvider = ImageFetchProvider.shared
        return try await imageFetchProvider.fetchImage(with: url)
    }
}

적용 결과

개선 되었는지 확인해보기 위해서 debug navigator 를 확인해보았다.

조건)

  • 아래로 끝까지 스크롤 한 후, 위로 다시 한번 스크롤하였다.
  • 캐싱된 이미지와 thumbnail 을 사용하여 CPU와 Memory 를 절약해보자.

thumbnail 사용 전)
2

3

thumbnail 사용 후)

4

55

…? thumbnail 을 사용한 후 CPU, Memory 둘 다 소폭 줄었지만.. 후반부에 CPU 를 더 많이 사용하는 것을 확인 할 수 있었다.(밑으로 내린 스크롤을 위로 올릴 때)

개선

func image(from urlPath: String) async throws -> UIImage? {
        guard let url = URL(string: Const.Path.imageURLPath + urlPath) else {
            throw ImageDownloadError.invalidURLString(Const.Path.imageURLPath + urlPath)
        }
        
        if let cached = cache[url] {
            return cached
        }
        
        let image = try await downloadImage(from: url)
        
        // 🔥 thumbnail 이미지를 캐싱해주도록 변경.
        // 🔥 이전에는 캐싱된 이미지는 그대로. 캐싱되지 않은 이미지는 thumbnail 로 변경해서 반환했다.(즉, 캐싱되지 않은 경우만 thumbnail 로 변경)
        guard let thumbnailImage = await image.thumbnail else { throw ImageDownloadError.unsupportImage }

        cache[url] = cache[url, default: thumbnailImage]
        
        return cache[url]!
    }

6

7

매번 결과가 동일하지는 않겠지만 전체적으로 보았을 때 CPU 와 Memory 가 확실히 줄었습니다.

느낀점

평소라면 그냥 예상되는 결과로써 넘어갈 수 있을 부분들인데 실제로 확인을 해보니까 간단하게 내가 작성한 코드가 얼마나 유효한지 알 수 있었다. 또한, 이를 통해서 원인을 찾고 코드도 개선할 수 있었다.

코드를 보고 결과를 예측하고, 시뮬레이터에서 느낌적으로 알고 넘어가던 것들을 실제로 수치적으로 확인할 수 있어서 코드로 결과를 기대하는 것이 아닌 실질적인 결과를 얻을 수 있어서 뿌듯했다.

출처:

Apple Developer Documentation - preparethumbnail

@hyun99999 hyun99999 added the documentation Improvements or additions to documentation label Jun 29, 2022
@hyun99999 hyun99999 self-assigned this Jun 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

1 participant