Skip to content

asnyc/await ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€์˜ thumbnail ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋งŒ๋“ค๊ธฐย #12

@hyun99999

Description

@hyun99999
  • 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

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentation

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions