Swift 5.5์—์„œ ์†Œ๊ฐœ๋œ Async/Await์— ๋Œ€ํ•ด ๊ณต๋ถ€ํ•ด๋ณธ๋‹ค.

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ์ด์œ 

thumbnail์„ fetchํ•˜๋Š” method๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•˜์ž.

  1. thumbnailURLRequest: ๋ฐ›๋Š” String์„ ๋ฐ”ํƒ•์œผ๋กœ URL Request ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ฆ
  2. dataTask: request๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋„คํŠธ์›Œํฌ ์š”์ฒญ
  3. UIImage(data): ๋ฐ›์€ ์š”์ฒญ์„ ๋ฐ”ํƒ•์œผ๋กœ imageํ™”
  4. prepareThumbnail: ํ™”๋ฉด์— ๋ณด์—ฌ์ง€๊ธฐ ์ „ image ์ฒ˜๋ฆฌ

์œ„์˜ 4๋‹จ๊ณ„์ค‘ 2๋‹จ๊ณ„์ธ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์˜ ๊ฒฝ์šฐ ๋‹ค๋ฅธ ์ž‘์—…์— ๋น„ํ•ด ์ƒ๋‹นํžˆ ์ง€์—ฐ์ด ๋งŽ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ์ž‘์—…์„ ๋‹ค๋ฅธ thread์—์„œ ๋Œ๋ฆฌ์ง€ ์•Š์œผ๋ฉด, ํ˜„์žฌ ์ž‘์—…์ด ์ง„ํ–‰๋˜๊ณ  ์žˆ๋Š” thread๊ฐ€ block ๋œ๋‹ค. ์ด๋Š” ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์— ์•…์˜ํ–ฅ์„ ์ฃผ๊ณ , ๋ฆฌ์†Œ์Šค๋ฅผ ๋‚ญ๋น„ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

Completion Handler

์ด๋Ÿฐ ์ƒํ™ฉ์—์„œ concurrent programming์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์šฐ๋ฆฌ๋Š” completion handler๋ฅผ ์‚ฌ์šฉํ•ด์™”๋‹ค.

func fetchThumbnail(for id: String, completion: @escaping (UIImage?, Error?) -> Void) {
    let request = thumbnailURLRequest(for: id)
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            completion(nil, error) // ๐Ÿ‘Ž
        } else if (response as? HTTPURLResponse)?.statusCode != 200 {
            completion(nil, FetchError.badID) // ๐Ÿ‘Ž
        } else {
            guard let image = UIImage(data: data!) else {
                return // ๐Ÿ‘Ž ??? ๋ˆ„๋ฝ (1)
            }
            image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
                guard let thumbnail = thumbnail else {
                    return // ๐Ÿ‘Ž ??? ๋ˆ„๋ฝ (2)
                }
                completion(thumbnail, nil) // ๐Ÿ‘
            }
        }
    }
    task.resume()
}

์ž˜ ๋œ ๊ฒƒ ๊ฐ™์ง€๋งŒ, ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค. image ๋ณ€ํ™˜์ด ๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜(1), thumbnail์˜ ๋ณ€ํ™˜์ด ์ž˜ ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์€ ๊ฒฝ์šฐ(2)์— completion handler์— nil์„ ์ „๋‹ฌํ–ˆ์–ด์•ผ ํ–ˆ๋Š”๋ฐ, ์•„๋ฌด๋Ÿฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š์•˜๋‹ค. ์ด๋Ÿด ๊ฒฝ์šฐ, ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์ชฝ์—์„œ๋Š” image๊ฐ€ ๋ณด์ด์ง€ ์•Š์•„ spinner๊ฐ€ ๊ณ„์†ํ•ด์„œ ๋Œ์•„๊ฐ€๊ณ  ์žˆ๋Š” ์ƒํƒœ์ผ ๊ฒƒ์ด๋‹ค.

func fetchThumbnail(for id: String, completion: @escaping (UIImage?, Error?) -> Void) {
    let request = thumbnailURLRequest(for: id)
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            completion(nil, error) // ๐Ÿ‘Ž
        } else if (response as? HTTPURLResponse)?.statusCode != 200 {
            completion(nil, FetchError.badID) // ๐Ÿ‘Ž
        } else {
            guard let image = UIImage(data: data!) else {
                completion(nil, FetchError.badImage) // ๐Ÿ‘Ž
                return 
            }
            image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
                guard let thumbnail = thumbnail else {
                    completion(nil, FetchError.badImage) // ๐Ÿ‘Ž
                    return 
                }
                completion(thumbnail, nil) // ๐Ÿ‘
            }
        }
    }
    task.resume()
}

๋‹น์žฅ์€ ์œ„์™€ ๊ฐ™์ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฌธ์ œ๋Š” completion handler์˜ ํ˜ธ์ถœ์ด, ์ „์ ์œผ๋กœ ๊ฐœ๋ฐœ์ž์˜ ์ฑ…์ž„์ด๋ผ๋Š” ๊ฒƒ์ด๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ํ•ด์ค„ ์ˆ˜๊ฐ€ ์—†๋‹ค. ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ฒŒ๋˜๋ฉด ์–ด๋””์„œ ์ž‘์„ฑ์„ ๊นŒ๋จน์—ˆ๋Š”์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์›Œ ๋””๋ฒ„๊น…๋„ ์–ด๋ ค์›Œ์ง„๋‹ค.

Result Type

func fetchThumbnail(for id: String, completion: @escaping (Result<UIImage, Error>) -> Void) {
    let request = thumbnailURLRequest(for: id)
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            completion(.failure(error)) // โœ…
        } else if (response as? HTTPURLResponse)?.statusCode != 200 {
            completion(.failure(FetchError.badID)) // โœ…
        } else {
            guard let image = UIImage(data: data!) else {
                completion(.failure(FetchError.badImage)) // โœ…
                return 
            }
            image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
                guard let thumbnail = thumbnail else {
                    completion(.failure(FetchError.badImage)) // โœ…
                    return 
                }
                completion(.success(thumbnail)) // โœ…
            }
        }
    }
    task.resume()
}

์œ„์˜ ์ฝ”๋“œ๋ณด๋‹ค ์•ฝ๊ฐ„ ๋” ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๊ธด ํ•˜๋‹ค. Result Type์„ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ ๋” ๋ชป์ƒ๊ฒจ์ง€๊ณ  ๊ธธ์–ด์ ธ๋ฒ„๋ ธ๋‹ค. Future์™€ ๊ฐ™์€ ๋ฐฉ์‹์„ ํ†ตํ•ด ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•˜๋ ค๋Š” ๋…ธ๋ ฅ๋“ค๋„ ์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ์‰ฝ๊ณ , ๊ฐ„๋‹จํ•˜๋ฉฐ, ์•ˆ์ „ํ•œ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์ง€๋Š” ๋ชปํ–ˆ๋‹ค.

Async/Await

func fetchThumbnail(for id: String) โœ… async โœ… throws -> UIImage {
    let request = thumbanilURLRequest(for: id)
    let (data, response) = โœ… try โœ… await URLSession.shared.data(for: request)
    guard (response as? HTTPURLResponse)?.statusCode == 200 else {
        throw FetchError.badID
    }
    let maybeImage = UIImage(data: data)
    guard let thumbnail = โœ… await maybeImage?.thumbnail else {
        throw FetchError.badIamge
    }
    return thumbnail
}
  1. async: ๋น„๋™๊ธฐ๋กœ ๋กœ์ง์ด ์ฒ˜๋ฆฌ๋  ๊ฑฐ์•ผ
  2. throws: ์‹คํŒจํ•˜๋ฉด ์—๋Ÿฌ๋ฅผ ๋˜์งˆ ๊ฑฐ์•ผ
  3. try: dataMethod๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋˜์ง€๋Š” ํ•จ์ˆ˜๋ผ ๋ฐ›์•„์ค˜์•ผ ํ•œ๋‹ค.
  4. await: ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ๋˜๊ณ , ๊ฒฐ๊ณผ๊ฐ’์ด ์˜ฌ ๋•Œ๊นŒ์ง€ ์ž‘์—… ์ง„ํ–‰์‚ฌํ•ญ์„ ๋ฉˆ์ถฐ์ค˜
    • ํ•ด๋‹น ๋‹จ๊ณ„์—์„œ ์ž‘์—… thread๋Š” suspend๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ณ , ์ž์œ ๋กญ๊ฒŒ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  5. Property๋„ async ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ ์‚ฌ์šฉํ•˜๋Š” ์ชฝ์—์„œ await ํ‚ค์›Œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค.
    • initializer๋„ async ํ•  ์ˆ˜ ์žˆ๋‹ค.

20์ค„ ์งœ๋ฆฌ ์ฝ”๋“œ๊ฐ€ 5์ค„๋กœ ์ค„์—ˆ๋‹ค. ์ฝ”๋“œ๋„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฝํžŒ๋‹ค.

Property Async

์œ„์—์„œ 5๋ฒˆ ํ•ญ๋ชฉ์—์„œ Property๋„ asyncํ•  ์ˆ˜ ์žˆ๋‹ค ํ–ˆ๋Š”๋ฐ, ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜๋Š”์ง€ ์‚ดํŽด๋ณด์ž.

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

์˜ค์ง ์ฝ๊ธฐ ์ „์šฉ Property๋งŒ์ด async ํ‚ค์›Œ๋“œ๋ฅผ ๋‹ฌ ์ˆ˜ ์žˆ๋‹ค.

Async Sequences

initializer, property, function ์ด์™ธ์—๋„ async ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฐ”๋กœ for loop์ด๋‹ค.

for await id in staticImageIDsURL.lines {
    let thumbnail = await fetchThumbnail(for: id)
    collage.add(thumbnail)
}
let result = await collage.draw()

์ด ๋ถ€๋ถ„์€ ๋‹ค์Œ ๊ธ€์—์„œ ๋‹ค๋ฃจ๋„๋ก ํ•˜๊ฒ ๋‹ค.

Sync & Async

await ํ‚ค์›Œ๋“œ๋กœ ๋œ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด, ์ง€๊ธˆ๊นŒ์ง€ ์ž‘์—…ํ•˜๊ณ  ์žˆ๋Š” ์ œ์–ด๊ถŒ์€ system์œผ๋กœ ๋„˜์–ด๊ฐ„๋‹ค. system์—์„œ๋Š” ํ˜„์žฌ ์ž‘์—… ์ƒํ™ฉ๊นŒ์ง€ suspend๋œ ์นœ๊ตฌ ๋ง๊ณ , ๋” ์ค‘์š”ํ•œ ๋…€์„์„ ๋„˜๊ฒจ๋ฐ›์€ ์ œ์–ด๊ถŒ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.

system์œผ๋กœ code block์ด ๋„˜์–ด๊ฐ”์„ ๋•Œ, ๋ฐ”๋กœ ์‹คํ–‰๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค. ๋จผ์ € ์Œ“์—ฌ์žˆ๋Š” ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•œ ํ›„์—์•ผ ์‹คํ–‰๋œ๋‹ค. completion handler์˜ ๋™์ž‘๊ณผ ๊ฐ™๋‹ค. ํ•˜์ง€๋งŒ ์ด ๊ณผ์ •์—์„œ await ํ‚ค์›Œ๋“œ๋ฅผ ํ†ตํ•ด ํ•˜์œ„์— ์ž‘์„ฑ๋œ instruction๊นŒ์ง€ ํ•˜๋‚˜์˜ transaction์œผ๋กœ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. await ํ‚ค์›Œ๋“œ๋ฅผ ํ™•์ธํ•˜๋Š” ์ˆœ๊ฐ„, ํ•ด๋‹น ์ž‘์—… ํ๋ฆ„์ด suspend๋  ์ˆ˜ ์žˆ๊ณ , ๊ทธ ์‚ฌ์ด์— ๋‹ค๋ฅธ ์ž‘์—…๋“ค์„ ์ฒ˜๋ฆฌํ•˜๊ฒ ๊ตฌ๋‚˜~ ํ•˜๊ณ  ์ธ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

Summary

  1. async keyword๋Š” ํ•จ์ˆ˜๋ฅผ suspend ํ•˜๋„๋ก ํ•œ๋‹ค.
  2. await keyword๋Š” async function์ด ์‹คํ–‰์„ suspendํ•  ์ˆ˜ ์žˆ์Œ์„ ํ‘œ์‹œํ•œ๋‹ค.
  3. suspend๋˜๋Š” ๋™์•ˆ ๋‹ค๋ฅธ ์ž‘์—…์ด ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋‹ค.
  4. ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ๋Š” async function์ด ์™„๋ฃŒ๋˜๋ฉด await ์ดํ›„ ๊ณผ์ •์ด ์‹คํ–‰๋œ๋‹ค.

Bridging from sync to async

async ํ•จ์ˆ˜๋ฅผ call ํ•˜๊ฒŒ ๋˜๋ฉด, callํ•˜๋Š” ์ชฝ์—์„œ ์œ„์™€ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋œฌ๋‹ค. async ํ•จ์ˆ˜์˜ ๊ฒฝ์šฐ์—๋Š” ์ƒ์œ„ ํ˜ธ์ถœ ํ•จ์ˆ˜๋„ async ํ‚ค์›Œ๋“œ๋ฅผ ๋‹ฌ์•„์ฃผ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด๋Ÿฐ ๊ฒฝ์šฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ async Task function์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

class ViewController: UIViewController {
 
    override func viewDidLoad() {
        super.viewDidLoad()
 
        Task {
            await self.asyncFuntion()
        }
    }
 
    internal func asyncFuntion() async {
        print("asyncFuntion!!")
    }
 
}

์ด๋Š” ์šฐ๋ฆฌ๊ฐ€ ์ด์ „์— ์‚ฌ์šฉํ•˜๋˜ global dispatch queue์˜ async ํ•จ์ˆ˜์™€ ๋น„์Šทํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค. ํ•ด๋‹น ์ž‘์—…์„ packageํ™”ํ•˜์—ฌ ๋‹ค์Œ thread์—์„œ ์ฆ‰์‹œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์‹œ์Šคํ…œ์œผ๋กœ ์ „์†กํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด, async code๋ฅผ sync context์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

Reference