[iOS][WWDC] Explore structured concurrency in Swift

Hyunndyยท2023๋…„ 2์›” 15์ผ
0

iOS-Concurrency

๋ชฉ๋ก ๋ณด๊ธฐ
3/4

๐Ÿธ

์–ด์ œ Async/await ์„ธ์…˜์„ ๋ณด์•˜๋Š”๋ฐ์š”.

Async ํ•จ์ˆ˜๋Š” suspend ๋  ์ˆ˜ ์žˆ๊ณ , suspend ๋˜๋ฉด ํ˜ธ์ถœํ•œ ํ•จ์ˆ˜๋„ suspend ๋˜๊ธฐ ๋•Œ๋ฌธ์—
async ํ‚ค์›Œ๋“œ๊ฐ€ ๋ถ™์€ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด

  • ์‹คํ–‰ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ๋น„๋™๊ธฐ(Async) ์ด๊ฑฐ๋‚˜
  • Async Task Function ์•ˆ์—์„œ ์‹คํ–‰

ํ•ด์•ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ ์ค‘ Async Task Block์— ๊ด€ํ•ด ์•Œ์•„๋ณด๊ธฐ ์œ„ํ•ด์„œ ์˜ค๋Š˜์€ ์ด ์„ธ์…˜์„ ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
Explore structured concurrency in Swift


Concurrency

์„ธ์…˜ ์‹œ์ž‘์ „์—, Concurrency๋Š” ์ €๋ฒˆ ๊ธ€์—์„œ ์ •๋ฆฌํ•œ ์šฉ์–ด ์ž…๋‹ˆ๋‹ค.
Queue(์ž‘์—… ๋Œ€๊ธฐ ํ–‰๋ ฌ)์˜ ์ž‘์—… ์ฒ˜๋ฆฌ ๋ฐฉ์‹์— ๊ด€ํ•œ ์šฉ์–ด ์ค‘ ํ•˜๋‚˜์˜€์ฃ !

  • Serial (์ง๋ ฌ)
  • Concurrency (๋™์‹œ)

Concurrency๋Š” ๋™์‹œ ์‹คํ–‰ ๋œ๋‹ค๋Š” ๋œป์œผ๋กœ,
ConcurrentQueue๋Š” ๋“ค์–ด์˜จ ์ž‘์—…์„ ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋‹ค๋ฅธ Thread์—์„œ ๋™์‹œ์— ์‹คํ–‰ ์‹œํ‚ต๋‹ˆ๋‹ค.


Structured Programming

Swift 5.5 ๋ถ€ํ„ฐ structured concurrency(๊ตฌ์กฐํ™”๋œ ๋™์‹œ์„ฑ) ์ด๋ผ๋Š” ๊ฐœ๋…์„ ์‚ฌ์šฉํ•˜์—ฌ concurrent Program(๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋žจ)์„ ์ž‘์„ฑํ•˜๋Š” ์ƒˆ๋กœ์šด ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

structured Programming.. ๊ตฌ์กฐ์  ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด๋ž€ ๋ฌด์—‡์ผ๊นŒ?

์ปดํ“จํŒ… ์ดˆ๊ธฐ์—๋Š” control-flow(์ œ์–ด ํ๋ฆ„)๊ฐ€ ๋ชจ๋“ ๊ณณ์—์„œ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ์ผ๋ จ์˜ ๋ช…๋ น์œผ๋กœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋กœ๊ทธ๋žจ์„ ์ฝ๊ธฐ๊ฐ€ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ! ์š”์ฆ˜์—” ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์–ธ์–ด๊ฐ€ control-flow(์ œ์–ด ํ๋ฆ„)์„ ๋ณด๋‹ค ๊ท ์ผํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด structured Programming(๊ตฌ์กฐํ™”๋œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ)์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋”์ด์ƒ ์ €๋ ‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ๋“ค๋ฉด..

if ๋ธ”๋ก์€ ๊ตฌ์กฐํ™”๋œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
์ค‘์ฒฉ๋œ ์ฝ”๋“œ ๋ธ”๋ฝ์ด ์œ„ -> ์•„๋ž˜๋กœ ์ด๋™ํ•˜๋Š” ๋™์•ˆ์—๋งŒ ์กฐ๊ฑด๋ถ€๋กœ ์‹คํ–‰๋˜๋„๋ก ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

Swift ์—์„œ, then Block์€ static scoping(์ •์  ๋ฒ”์œ„ ์ง€์ •)๋„ ์ค€์ˆ˜ํ•ฉ๋‹ˆ๋‹ค.
์ฆ‰, ๋ณ€์ˆ˜๋ช…์ด ๋ธ”๋ก์— ์ •์˜๋œ ๊ฒฝ์šฐ์—๋งŒ ๋ณ€์ˆ˜๋ช…์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋ธ”๋ก์•ˆ์— ์ •์˜๋œ ๋ชจ๋“  ๋ณ€์ˆ˜์˜ ์ˆ˜๋ช…์€ ๋ธ”๋ก์ด ๋– ๋‚  ๋•Œ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก ์€, static scope(์ •์  ๋ฒ”์œ„)๋ฅผ ์‚ฌ์šฉํ•œ structured programming์€ control-flow(์ œ์–ด ํ๋ฆ„)๊ณผ variable lifetime(๋ณ€์ˆ˜ ์ˆ˜๋ช…)์„ ์•Œ๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

๋ณด๋‹ค ์ผ๋ฐ˜์ ์œผ๋กœ, ๊ตฌ์กฐํ™”๋œ ์ œ์–ดํ๋ฆ„์€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ˆœ์„œ๋ฅผ ์ง€์ •ํ•˜๊ณ  ํ•จ๊ป˜ ์ค‘์ฒฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๊ฒƒ์ด Structured Programming์˜ ๊ธฐ์ดˆ ์ž…๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ๊ด„ํ˜ธ(static scope)๋ฅผ ์—ด๊ณ  Top-down์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ฝ์œผ๋ฉด์„œ ์—ฌ๋Ÿฌ scope๋“ค์„ ์™”๋‹ค๊ฐ”๋‹ค ์ฝ๋Š”๊ฒƒ์ด ๋งค์šฐ ์ง๊ด€์ ์ด๊ณ  ๋‹น์—ฐํ•˜๊ฒŒ ์—ฌ๊ธฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค!

ํ•˜์ง€๋งŒ..๐Ÿ˜ฑ
์š”์ฆˆ์Œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ asynchronous(๋น„๋™๊ธฐ์  ์ž‘์—… ์ˆœ์„œ) + concurrent(๋™์‹œ์— ์—ฌ๋Ÿฌ ์ž‘์—… ์‹คํ–‰) ๋ฅผ ํŠน์ง•์œผ๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— structured Programming์„ ๋” ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

์™œ๋ƒ๋ฉด ์–ด๋– ํ•œ ๊ตฌ์กฐ๋ฅผ ์–ด๋– ํ•œ ์ˆœ์„œ์— ๋งž๊ฒŒ control-flow๊ฐ€ ์ƒ๊ฒจ๋‚˜์•ผ ํ•˜๋Š”๋ฐ... ๋น„๋™๊ธฐ์™€ ๋™์‹œ์„ฑ์€ ๊ทธ๊ฑธ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์œผ๋‹ˆ๊นŒ์š”


Structured Programming + asynchronous Code

Asynchronous ํ•œ ์ฝ”๋“œ๋ฅผ ์–ด๋–ป๊ฒŒ structued programming ์œผ๋กœ ๋ฐ”๊พธ๋Š”์ง€ ๋ด…์‹œ๋‹ค.

    /*
     Asynchronous code with completion Handlers is unstructured
     */
    func fetchThumbnail(for ids: [String], completion handler: @escaping ([String: UIImage]?, Error?) -> Void) {
        
        guard let id = ids.first else {
            handler([:], nil)
            return
        }

        let request = URLRequest(url: URL(string: id)!)

        let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
            if let error {
                handler(nil, error)
            } else if (response as? HTTPURLResponse)?.statusCode != 200 {
                handler(nil, HyunndyError.noImage)
            } else {
                // MARK: 3๋ฒˆ
                guard let image = UIImage(data: data!) else {
                    handler(nil, HyunndyError.noImage)
                    return
                }
                
                // MARK: 4๋ฒˆ
                image.prepareThumbnail(of: CGSize(width: 40.0, height: 40.0), completionHandler: { thumbnail in
                    guard let thumbnail else {
                        handler(nil, HyunndyError.noImage)
                        return
                    }
                    
                    fetchThumbnail(for: Array(ids.dropFirst()), completion: { thumbnails, error in
                        // .... ad image to thumbnails .....
                    })
                })
            }
        })
        
        task.resume()
    }

์ด ์ฝ”๋“œ๋Š” structured ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์™œ๋ƒ?

CompletionHandler๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด์˜ ๊ฒฐ๊ณผ๋กœ,
์ด ํ•จ์ˆ˜๋Š” error Handling์„ ์œ„ํ•ด ๊ตฌ์กฐํ™”๋œ ์ œ์–ด ํ๋ฆ„(structured control-flow)๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
์™œ๋ƒ๋ฉด CompletionHandler๋Š” ๊ทธ๋ƒฅ throw๋œ Error๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฒŒ ์˜๋ฏธ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
(๋ญ”๊ฐ€ ์ œ์–ดํ๋ฆ„์œผ๋กœ์จ ๊ด€๋ฆฌ๋ฅผ ํ•œ๋‹ค๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ๊ทธ๋ƒฅ Error๋ฅผ ๋˜์ง€๊ณ  ๊ทธ๊ฒƒ๋งŒ ์ฒ˜๋ฆฌํ•œ๋‹ค์˜ ์˜๋ฏธ์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค)

๋˜..
์š” ํ•จ์ˆ˜๋Š” Array์•ˆ์— ๊ฐ€๋“๋“ค์–ด์žˆ๋Š” url๊ณผ ๋งค์นญ๋˜๋Š” ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด์„œ CompletionHandler์— ๋„˜๊ฒจ์ค˜์•ผํ•˜๋Š”๋ฐ, ๊ทธ๋Ÿฌ๋ ค๋ฉด ์ค‘์ฒฉ์„ ์จ์•ผํ•ฉ๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ? CompletionHandler์˜ ํ˜•ํƒœ ๋•Œ๋ฌธ์— ์žฌ๊ท€ ํ•จ์ˆ˜๋ฅผ ์“ฐ๊ณ  ์žˆ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.
์ œ์–ด ํ๋ฆ„์ด ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š๋Š”๋‹ค๋Š” ์†Œ๋ฆฌ์ง€์š”.

์ด์ œ ์ด๊ฑธ Structured Programming์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” Async/await ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋‹ค์‹œ ์ž‘์„ฑํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

    func fetchThumbnail2(for ids: [String]) async throws -> [String: UIImage] {
        var thumbnails: [String: UIImage] = [:]
        
        for id in ids {
            let request = URLRequest(url: URL(string: id)!)
            let (data, response) = try await URLSession.shared.data(for: request)
            
            try validateResponse(response)
            guard let image = await UIImage(data: data)?.byPreparingThumbnail(ofSize: CGSize(width: 40.0, height: 40.0)) else {
                throw HyunndyError.noImage
            }
            
            thumbnails[id] = image
        }
        
        return thumbnails
    }
    

TA-DA-!
์šฐ๋ฆฐ ์ด๋ฏธ Meet Async/await์„ ๋ณด๊ณ ์™”๊ธฐ ๋•Œ๋ฌธ์— ๋”ฐ๋กœ ์„ค๋ช…ํ•˜์ง„ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์ถ”๊ฐ€๋กœ..

  • ์ˆ˜ ์ฒœ๊ฐœ์˜ ์ด๋ฏธ์ง€์— ๋Œ€ํ•œ ์ธ๋„ค์ผ์„ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—” ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ์š”? ์ธ๋„ค์ผ์„ ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์”ฉ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ๋”์ด์ƒ ์ด์ƒ์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ธ๋„ค์ผ์„ ๊ณ ์ • ํฌ๊ธฐ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ URL์—์„œ ๋‹ค์šด๋กœ๋“œ ํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ์š”?

์ด์ œ! ์šฐ๋ฆฌ๋Š” concurrency๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์—ฌ๋Ÿฌ ๋‹ค์šด๋กœ๋“œ๊ฐ€ ๋ณ‘๋ ฌ(parrel)๋กœ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์šฐ๋ฆฌ๋Š” ํ”„๋กœ๊ทธ๋žจ์— Concurrency(๋™์‹œ์„ฑ)์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด Additional Task๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


Task

Task๋Š” async ํ•จ์ˆ˜์™€ ํ•จ๊ป˜ ๋™์ž‘ํ•˜๋Š” Swift์˜ ์‹ ๊ธฐ๋Šฅ ์ž…๋‹ˆ๋‹ค.
asyncํ•œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ Freshํ•œ execution(์‹คํ–‰) context๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
๊ฐ Task๋Š” ๋‹ค๋ฅธ execution Task์™€ ๊ด€๋ จํ•˜์—ฌ concurrently(๋™์‹œ์—) ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

์ฆ‰, Task๋Š” App์˜ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ(execution contenxt, ์ž‘์—… ํ”Œ๋กœ์šฐ)๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

์•ˆ์ „ํ•˜๊ณ  ํšจ์œจ์ ์ธ ํƒ€์ด๋ฐ์— parallel(๋ณ‘๋ ฌ)๋กœ ์‹คํ–‰๋˜๋„๋ก ์ž๋™์œผ๋กœ ์Šค์ผ€์ฅด๋ง ๋ฉ๋‹ˆ๋‹ค.
Async ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด๋„ ์ƒˆ๋กœ์šด Task๊ฐ€ ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š๋Š”๋‹ค๋Š”๊ฑธ ์œ ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค! Task๋Š” ๋ช…์‹œ์ ์œผ๋กœ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

structed concurrency๋Š” ์œ ์—ฐ์„ฑ๊ณผ ๋‹จ์ˆœ์„ฑ ์‚ฌ์ด์˜ ๊ท ํ˜•์— ๊ด€ํ•œ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— Swift์—๋Š” ๋ช‡๊ฐ€์ง€ ๋‹ค๋ฅธ Task๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ•จ๊ป˜ ๋ณด์‹œ์ฃ !

Async-let tasks

Async-let ๋ฐ”์ธ๋”ฉ์ด๋ผ๋Š” ์ƒˆ๋กœ์šด ๊ตฌ๋ฌธ ํ˜•์‹์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ Task ์ž…๋‹ˆ๋‹ค.


๋จผ์ € ๊ธฐ๋ณธ์ ์ธ let Binding์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ Flow๋กœ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.

์ด Flow์—์„œ ๋‹ค์šด๋กœ๋“œ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์œผ๋‹ˆ ํ”„๋กœ๊ทธ๋žจ์ด ๋‹ค์šด๋กœ๋“œ๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค์ œ๋กœ ํ•„์š”ํ•  ๋•Œ ๊นŒ์ง€ ๋‹ค๋ฅธ ์ž‘์—…์„ ํ•˜๊ธธ ์›ํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ!! ์šฐ๋ฆฌ๋Š” ๊ธฐ์กด let ์•ž์— async๋งŒ ๋ถ™์—ฌ์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฐ ํ˜•ํƒœ๋ฅผ async-let concurrent binding ์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

concurrent binding๊ณผ sequential binding์€ ๋งค์šฐ ๋‹ค๋ฆ…๋‹ˆ๋‹ค!

concurrent binding์„ ํ‰๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด, Swift๋Š” new child Task๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
์ด ์ž‘์—…์€ ํ˜ธ์ถœํ•œ(์ƒ์„ฑํ•œ) Task์˜ ํ•˜์œ„ Task ์ž…๋‹ˆ๋‹ค.

๋ชจ๋“  Task๋Š” App์˜ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ์ด๋ฏ€๋กœ, 2๊ฐœ์˜ ํ™”์‚ดํ‘œ๊ฐ€ ํ•œ ๋ฒˆ์— ๋‚˜์˜ต๋‹ˆ๋‹ค.

  • ๋ฐ์ดํ„ฐ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์ฆ‰์‹œ ์‹œ์ž‘ํ•˜๋Š” Chid Task๋ฅผ ์œ„ํ•œ ๊ฒƒ
  • Parent Task๋ฅผ ์œ„ํ•œ๊ฒƒ์œผ๋กœ, ๋ณ€์ˆ˜ ๊ฒฐ๊ณผ๋ฅผ placeholder์— ์ฆ‰์‹œ ๋ฐ”์ธ๋”ฉ ํ•˜๋Š” ๊ฒƒ(๋‹ค์Œ์œผ๋กœ ๋„˜์–ด๊ฐ€๊ธฐ ์œ„ํ•ด)

Child Task๊ฐ€ Concurrent(๋™์‹œ)์— ๋‹ค์šด๋กœ๋“œ๋ฅผ ํ•˜๋Š” ๋™์•ˆ,
Parent Task๋Š” Following statements(๋‹ค์Œ ๋™์ž‘)๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ!!!! ๊ฒฐ๊ณผ์˜ ์‹ค์ œ ๊ฐ’์ด ํ•„์š”ํ•œ statements์— ๋„๋‹ฌํ•˜๋ฉด Parent Task๋Š” placeholder์„ ์ถฉ์กฑํ•˜๋Š” Child Task์˜ ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค!

๋‘ ๊ฐœ์˜ ์„œ๋กœ ๋‹ค๋ฅธ URL์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉฐ async-let concurrent binding์— ๋Œ€ํ•ด ๋ด…์‹œ๋‹ค.

    /// Structured Concurrency with seuquential binding
    func fetchOneThumbnail(withId id: String) async throws -> UIImage {
        let imageReq = URLRequest(url: URL(string: id)!), metaReq = URLRequest(url: URL(string: id)!)
        
        let (data, _) = try await URLSession.shared.data(for: imageReq)
        let (metadata, _) = try await URLSession.shared.data(for: metaReq)
        
        guard
            let size = parseSize(from: metadata),
            let image = await UIImage(data: data)?.byPreparingThumbnail(ofSize: size)
        else {
            throw HyunndyError.noImage
        }
        
        return image
    }

let์˜ ์˜ค๋ฅธ์ชฝ์— try await์„ ์‚ฌ์šฉํ•˜๋Š” Sequential Binding(์ˆœ์ฐจ ๋ฐ”์ธ๋”ฉ) ์ž…๋‹ˆ๋‹ค.
try await์„ ํ•˜๋Š”์ชฝ์—์„œ Error๋‚˜ Suspend๊ฐ€ Observing ๋˜๋Š” ์œ„์น˜์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
์—ฌ๊ธฐ์„œ๋Š” download๊ฐ€ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์šฐ๋ฆฐ ์š”! ๋‹ค์šด๋กœ๋“œ๋ฅผ ๋™์‹œ์—(Concurrently) ์ˆ˜ํ–‰ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.
Concurrent๋ฅผ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ•˜์ฃ ? Task๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
์šฐ๋ฆฌ๋Š” ๋ฐฉ๊ธˆ ๋ญ˜๋ฐฐ์› ์ฃ ? async-let Task์š”!

    /// Structured Concurrency with async-let concurrent binding
    func fetchOneThumbnail2(withId id: String) async throws -> UIImage {
        let imageReq = URLRequest(url: URL(string: id)!), metaReq = URLRequest(url: URL(string: id)!)
        
        /// async let์„ ์“ฐ๋ฉด ์ด์ œ ๋‹ค์šด๋กœ๋“œ๊ฐ€ Child Task์—์„œ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ ๋”์ด์ƒ ์˜ค๋ฅธ์ชฝ์— try await์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
        /// ํ•˜์œ„ Task, ์ƒ์œ„ Task์—์„œ ๋™์‹œ์— ๋ฐ”์ธ๋”ฉ๋œ ๋ณ€์ˆ˜๋ฅผ """์‚ฌ์šฉํ•  ๋•Œ"""" Parent Task์—์„œ๋งŒ Error๋‚˜ suspend๊ฐ€ Observing ๋ฉ๋‹ˆ๋‹ค.
        async let (data, _) = URLSession.shared.data(for: imageReq)
        async let (metadata, _) = URLSession.shared.data(for: metaReq)
        
        /// ๋”ฐ๋ผ์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์— try await์„ ๋ถ™์ž…๋‹ˆ๋‹ค.
        guard
            let size = parseSize(from: try await metadata),
            let image = try await UIImage(data: data)?.byPreparingThumbnail(ofSize: size)
        else {
            throw HyunndyError.noImage
        }
        
        return image
    }
    

async-let์„ ํ†ตํ•ด concurrency Binding์œผ๋กœ ๋ฐ”๋€ ์ฝ”๋“œ ์ž…๋‹ˆ๋‹ค.

์ด์ œ ๋‹ค์šด๋กœ๋“œ๋Š” Child Task๋“ค์—์„œ ๋™์‹œ์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ Error๋‚˜ Suspend๊ฐ€ Child Task์—์„œ ๋ฐœ์ƒํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, async let์„ ๋ถ™์ธ ๋ณ€์ˆ˜ ์˜†์—์„œ try await์„ ์ง€์›Œ์ค๋‹ˆ๋‹ค.

๊ทธ๋Ÿผ ์–ด๋”” ๋„ฃ์–ด์ฃผ๋Š๋ƒ?
์œ„์— Flow ๊ธฐ์–ต๋‚˜์‹œ์ฃ ?
Parent Task์™€ Child Task๊ฐ€ ๋™์‹œ์— Bindingํ•œ ๊ฐ’์„ ์‹ค์ œ๋กœ ์“ฐ๋Š” ๋ถ€๋ถ„์—์„œ Error๋‚˜ Suspend๋ฅผ Observe ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ๊ฐ’์„ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋Š”

       guard
            let size = parseSize(from: try await metadata),
            let image = try await UIImage(data: data)?.byPreparingThumbnail(ofSize: size)

์ด๊ณณ์— ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.


Structured task guarantees

์—ฌ๊ธฐ๊นŒ์ง€ ๋งํ•œ Child Task๋Š” ์‹ค์ œ๋กœ Task Tree ๋ผ๋Š” ๊ณ„์ธต ๊ตฌ์กฐ์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค.
ํ•˜๋‚˜์˜ Asynchronous ํ•จ์ˆ˜์—์„œ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋กœ ํ˜ธ์ถœํ•  ๋•Œ ๋งˆ๋‹ค, ๋™์ผํ•œ Task๊ฐ€ ํ˜ธ์ถœ์„ ์‹คํ–‰ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

    /// Structured Concurrency with async-let concurrent binding
    func fetchOneThumbnail2(withId id: String) async throws -> UIImage {
        let imageReq = URLRequest(url: URL(string: id)!), metaReq = URLRequest(url: URL(string: id)!)
        
        /// async let์„ ์“ฐ๋ฉด ์ด์ œ ๋‹ค์šด๋กœ๋“œ๊ฐ€ Child Task์—์„œ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ ๋”์ด์ƒ ์˜ค๋ฅธ์ชฝ์— try await์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
        /// ํ•˜์œ„ Task, ์ƒ์œ„ Task์—์„œ ๋™์‹œ์— ๋ฐ”์ธ๋”ฉ๋œ ๋ณ€์ˆ˜๋ฅผ """์‚ฌ์šฉํ•  ๋•Œ"""" Parent Task์—์„œ๋งŒ Error๋‚˜ suspend๊ฐ€ Observing ๋ฉ๋‹ˆ๋‹ค.
        async let (data, _) = URLSession.shared.data(for: imageReq)
        async let (metadata, _) = URLSession.shared.data(for: metaReq)
        
        /// ๋”ฐ๋ผ์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์— try await์„ ๋ถ™์ž…๋‹ˆ๋‹ค.
        guard
            let size = parseSize(from: try await metadata),
            let image = try await UIImage(data: data)?.byPreparingThumbnail(ofSize: size)
        else {
            throw HyunndyError.noImage
        }
        
        return image
    }
    

๋”ฐ๋ผ์„œ, fetchOneThumbnailํ•จ์ˆ˜๋Š” ํ•ด๋‹น Task์˜ ๋ชจ๋“  attribute๋ฅผ ์ƒ์†ํ•ฉ๋‹ˆ๋‹ค.
async-let๊ณผ ๊ฐ™์ด ์ƒˆ๋กœ์šด Task๋ฅผ ๋งŒ๋“ค ๋•Œ, ํ˜„์žฌ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰์ค‘์ธ Task์˜ ์ž์‹์ด ๋ฉ๋‹ˆ๋‹ค.

Task๋Š” ํŠน์ • function์˜ child๋Š” ์•„๋‹ˆ์ง€๋งŒ, ๊ทธ๋“ค์˜ ์ˆ˜๋ช…์€ ํ•ด๋‹น function์˜ ๋ฒ”์œ„๋กœ ์ง€์ •๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Task Tree๋Š” ํŠธ๋ฆฌ๊ตฌ์กฐ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•˜์œ„ Task๊ฐ€ ๋ชจ๋‘ ์™„๋ฃŒ๋˜์–ด์•ผ๋งŒ ์ƒ์œ„ Task๊ฐ€ ์™„๋ฃŒ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ทœ์น™์€ ์ž์‹ Task๊ฐ€ awaited๋˜์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๋น„์ •์ƒ์ ์ธ ์ œ์–ด ํ๋ฆ„์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.
์ด๊ฒŒ ๋ญ”๋ง์ด์•ผ?

        guard
            let size = parseSize(from: try await metadata),
            let image = try await UIImage(data: 

์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด, data๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋‹ค์šด๋กœ๋“œ๋ฅผ ๋จผ์ € ์‹œ์ž‘ํ–ˆ๋Š”๋ฐ, metaData ์ž‘์—…๋ถ€ํ„ฐ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
๊ทผ๋ฐ ๋งŒ์•ฝ data๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๋Š”๋ฐ์„œ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋ฉด ์–ด์ฉ”๊นŒ์š”?
Parent Task๋Š” data์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋„, metaData ์ž‘์—…์„ ๊ทธ๋ƒฅ ์ทจ์†Œ ์ƒํƒœ๋กœ ๋งŒ๋“ค์–ด๋ฒ„๋ฆฐ๋‹ค์Œ fetchThumbnail ํ•จ์ˆ˜๊ฐ€ ๋๋‚  ๋•Œ ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
Task๋ฅผ Cancel๋กœ ๋งˆํ‚นํ•ด๋„ Stop ์‹œํ‚ค์ง„ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋ƒฅ ๊ฒฐ๊ณผ๊ฐ€ ์ด์ œ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ƒ์œ„ Task๊ฐ€ ์‹คํŒจํ•˜๋ฉด ํ•˜์œ„ Task๋Š” ์ž๋™์œผ๋กœ ์ทจ์†Œ๋ฉ๋‹ˆ๋‹ค.
๊ฒฐ๊ณผ์ ์œผ๋กœ data๋ฅผ ๋‹ค์šด๋กœ๋“œ ํ•˜๋Š” Task๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ฉด, ํ•ด๋‹น ์ž‘์—…์€ Cancel๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
fetchThumnnail() ํ•จ์ˆ˜๋Š” ๋งˆ์ง€๋ง‰์— ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ฉด์„œ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค.

์Œ.. ์ด๋Ÿฐ

  • ์ƒ์œ„ Task์—์„œ Error๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ•˜์œ„ Task์™€ ์ƒ์œ„ Task ๋ชจ๋‘ Cancel ์ƒํƒœ๊ฐ€ ๋œ๋‹ค.
  • ๋ชจ๋“  Task๊ฐ€ Cancel ๋˜๊ฑฐ๋‚˜ Finish๋˜๋ฉด ํ˜ธ์ถœํ•œ Function๋„ ๋ชจ๋‘ ๋๋‚˜๊ธธ ๊ธฐ๋‹ค๋ฆฌ๋‹ค๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ถœํ•˜๋ฉฐ ์ข…๋ฃŒ๋œ๋‹ค.

(์ดํ•ดํ•œ๊ฒŒ ๋งž๋Š”์ง€ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ) ์ด ๋ณด์žฅ์€ structured Concurrency์˜ ๊ธฐ๋ณธ์ž…๋‹ˆ๋‹ค.
ARC๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ์ˆ˜๋ช…์„ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Task์˜ ์ˆ˜๋ช…์ฃผ๊ธฐ๋„ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ๊นŒ์ง€ Cancellation์˜ ์ „ํŒŒ์— ๊ด€ํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ์–ธ์ œ Task๊ฐ€ Stop ๋ฉ๋‹ˆ๊นŒ?
Task๊ฐ€ ํŠธ๋žœ์žญ์…˜ ์ค‘์ด๊ฑฐ๋‚˜, ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์ด ์—ด๋ ค์žˆ๋Š” ๊ฒฝ์šฐ Stop ์‹œํ‚ค๋Š”๊ฑด ์ข‹์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
-> ์š”๊ฒŒ ๋ฐ”๋กœ Cancellation์ด cooperativeํ•œ ์ด์œ  ์ž…๋‹ˆ๋‹ค.

Cancellation is cooperative

  • Task๋Š” cancel๋๋‹ค๊ณ  ๋ฐ”๋กœ stop ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • cancellation์€ ์–ด๋””์„œ๋‚˜ ์ฒดํฌ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • API๋ฅผ cancellation์„ ์—ผ๋‘ํ•ด๋‘๊ณ  ๋””์ž์ธํ•˜์„ธ์š”.

Cancellation..๊ทธ๋ž˜.
Task๊ฐ€ ์–ด๋–ป๊ฒŒ๋“  Error๊ฐ€ ๋‚˜์„œ Cancel ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•ฉ์‹œ๋‹ค.
๊ทธ๋Ÿผ ์ด๊ฑธ ์šฐ๋ฆฌ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ถ”์ ํ•  ๊ฒƒ์ธ๊ฐ€!

    /// Cancellation checking
    func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
        var thumbnails: [String: UIImage] = [:]
        for id in ids {
            /// Throws an error if the task was canceled.
            /// Task๊ฐ€ ์ทจ์†Œ๋œ ๊ฒฝ์šฐ Error์„ ๋ฐœ์ƒ์‹œ์ผœ for ๋ฃจํ”„๋ฅผ ํƒˆ์ถœ์‹œํ‚ต๋‹ˆ๋‹ค.
            /// Task๊ฐ€ cancel๋œ ๊ฒฝ์šฐ ์“ธ๋ชจ์—†๋Š” ํ†ต์‹ ์„ ๊ณ„์†ํ•ด์„œ App์ด crash ๋‚˜๊ฒŒ ๋ƒ…๋‘๊ณ  ์‹ถ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
            try Task.checkCancellation()
            if Task.isCancelled { break }
            thumbnails[id] = try await fetchOneThumbnail(withId: id)
        }
        return thumbnails
    }

try Task.checkCancellation()
if Task.isCancelled
๋“ฑ์œผ๋กœ Task๊ฐ€ Cancel๋œ ๊ฒƒ์„ ๊ฐ์ง€ํ•˜์—ฌ ๋ฌด์˜๋ฏธํ•œ Task ์‹คํ–‰์„ ํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿธ

์Œ..์†”์งํžˆ ์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ ์•ฝ๊ฐ„ ๋ฉ˜๋ถ•์™”์Šต๋‹ˆ๋‹ค.
๋‚œ ๊ทธ๋ƒฅ...Async/await ์„ธ์…˜์— ๋‚˜์™”๋˜ Task { } ๊ฐ€ ๊ถ๊ธˆํ–ˆ์„ ๋ฟ์ธ๋ฐ...
์™ ? ใ… ใ…  ์ด๊ฒŒ ๋ชจ์•ผ? ใ… ใ…  ์Šคํ‚ค ์ดˆ๊ธ‰ ์ฝ”์Šค ๋‚ด๋ ค์™”๋Š”๋ฐ ๊ฐ‘์ž๊ธฐ ์ค‘๊ธ‰์ฝ”์Šค ๋‚ด๋ ค๊ฐ€๋ผ๋Š” ๊ธฐ๋ถ„..?
์•„๋ฌดํŠผ ์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ๋Š” ์ดํ•ด๋ณด๋‹จ ์ •๋ง..๊ทธ๋ƒฅ WWDC ๋‚ด์šฉ์„ ๋‡Œ์— ์šฐ๊ฒจ๋„ฃ๊ธฐ ์œ„ํ•ด ์–ด๊ฑฐ์ง€๋กœ ํ•™์Šตํ•œ ๋‚ด์šฉ์„ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค..


Group Task

async-let Task๋Š” ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ concurrency๊ฐ€ ๊ณ ์ •๋˜์–ด์žˆ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ๋“ค์–ด์„œ..

    /// Async-let is for concurrency with static width
    func fetchThumbnails3(for ids: [String]) async throws -> [String: UIImage] {
        var thumbnails: [String: UIImage] = [:]
        for id in ids {
            thumbnails[id] = try await fetchOneThumbnail2(withId: id)
        }
        
        return thumbnails
    }

์ด ํ•จ์ˆ˜์—์„œ๋Š”

  • async let (data, _) = URLsession ~
  • async let (metadata, _) = URLSession ~

2๊ฐœ์˜ Child Task๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ ๋ฃจํ”„๊ฐ€ ๋Œ๊ธฐ ์ „์— ์ด 2๊ฐœ์˜ Task๊ฐ€ ์™„๋ฃŒ๋˜์•ผํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ..
์šฐ๋ฆฌ๊ฐ€ ๋ชจ๋“  ์ธ๋„ค์ผ์„ ๊ทธ๋ƒฅ ํ•œ ๋ฒˆ์— ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?
์—ฌ๊ธฐ์„œ๋Š” ids๋ผ๋Š” ์ •์  ๋ฐฐ์—ด์ด ์žˆ์—ˆ์ง€๋งŒ, ๋ฃจํ”„๊ฐ€ ๋ช‡ ๊ฐœ ๋Œ์ง€๋„ ๋ชจ๋ฅด๊ณ  ๋งŒ์•ฝ ๊ทธ๊ฒŒ ์—„์ฒญ ๋งŽ์•„์ง„๋‹ค๋ฉด?

์ด๋Ÿฐ ์ƒํ™ฉ์— ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋Š”๊ฒŒ Task Group ์ž…๋‹ˆ๋‹ค.

Task Group์€ dynamicํ•œ ์–‘์˜ concurrency๋ฅผ ์ œ๊ณตํ•˜๋„๋ก ์„ค๊ณ„๋œ structured concurrency์˜ ํ•œ ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค.

Task Group์€ withThrowingTaskGroup ํ•จ์ˆ˜๋กœ ํ˜ธ์ถœํ•˜์—ฌ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    func taskGroupFetchThumbnail(for ids: [String]) async throws -> [String: UIImage] {
        var thumbnail: [String: UIImage] = [:]
        
        try await withThrowingTaskGroup(of: Void.self, body: { group in
            for id in ids {
                group.addTask {
                    thumbnail[id] = try await self.fetchOneThumbnail2(withId: id)
                }
            }
        })
    }

withThrowingTaskGroup ํ•จ์ˆ˜๋Š”
Error์„ Throw ํ•  ์ˆ˜ ์žˆ๋Š” Child Task๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋ฒ”์œ„๊ฐ€ ์ง€์ •๋œ group object๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.
Group { } ์„ ๋‚˜๊ฐ€๋ฉด Task์˜ ์ˆ˜๋ช…์ด ๋‹คํ•˜๊ณ ,
๋ธ”๋ก ๋‚ด๋ถ€ ์•ˆ์— for-in loop๋ฅผ ๋ฐฐ์น˜ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— dynamiํ•œ ์–‘์˜ Task๋ฅผ ๋™์‹œ์— ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ˆœ์„œ์™€ ์ƒ๊ด€์—†์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ € ์ฝ”๋“œ๋Š” ์ปดํŒŒ์ผ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

thumbnail ๋”•์…”๋„ˆ๋ฆฌ๋Š” ํ•œ ๋ฒˆ์— ๋‘ ๊ฐœ ์ด์ƒ์˜ ์•ก์„ธ์Šค๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๋Š”๋ฐ Task Group ์•ˆ์—์„œ๋Š” ๋™์‹œ์— Task๊ฐ€ ๋งŽ์ด ์ƒ๊ฒจ๋‚˜๋ฏ€๋กœ data race ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค!

๊ณผ๊ฑฐ์—๋Š” ์ด ์˜ค๋ฅ˜๋“ค์„ ์ง์ ‘ ์ฐพ์•˜์–ด์•ผ ํ–ˆ๋Š”๋ฐ, ์ง€๊ธˆ์€ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์•Œ๋ ค์ค€๋‹ค๋Š”๋ฐ์š”.

@Sendable

์ด๊ฒƒ ๋•๋ถ„ ์ž…๋‹ˆ๋‹ค.
data-race safety ๋ผ๋Š” ๊ฐœ๋…์ธ๋ฐ..
Task์˜ ์ƒ์„ฑ์€ @Sendable ํด๋กœ์ €์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
@Sendable ํด๋กœ์ € ๋ณธ๋ฌธ์€ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ ๋ณ€์ˆ˜๋ฅผ ์บก์ณํ•˜๋Š”๊ฒƒ์ด ์ œํ•œ๋ฉ๋‹ˆ๋‹ค.
์™œ? Task๊ฐ€ ์‹คํ–‰๋œ ํ›„ ์ˆ˜์ • ๋  ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ์š”.
์ด ๋ง์€... Task ์•ˆ์—์„œ ์บก์ณ๋œ ๊ฐ’์€ ๊ณต์œ ํ•˜๊ธฐ์— ์•ˆ์ „ํ•œ value Type, actors, ์ž์ฒด ๋™๊ธฐํ™”๋ฅผ ๊ตฌํ˜„ํ•œ class ๊ฐ์ฒด ๊ฐ™์€ ๊ฒƒ์ด์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์‹œ ๋Œ์•„์™€์„œ!
์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

    // A task group is for concurrency with dynamic width
    func taskgroupFetchThumbnail(for ids: [String]) async throws -> [String: UIImage] {
        
        var thumbnails: [String: UIImage] = [:]
        try await withThrowingTaskGroup(of: (String, UIImage).self, body: { group in
            for id in ids {
                group.addTask {
                    return (id, try await self.fetchOneThumbnail2(withId: id))
                }
            }
    
            // ์ด for-await ๋ฃจํ”„๋Š” ์™„๋ฃŒ๋œ ์ˆœ์„œ๋Œ€๋กœ ์ž์‹ ์ž‘์—…์—์„œ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
            for try await (id, thumbnail) in group {
                thumbnails[id] = thumbnail
            }
        })
        return thumbnails
    }

withThrowingTaskGroup(of: ~)
์— ๋“ค์–ด๊ฐ€๋Š”๊ฒŒ group Task์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” ํƒ€์ž…์ด์—ˆ๊ตฐ์š”.

Group Task์•ˆ์—์„œ ๋ฃจํ”„ ์•ˆ์— ์žˆ๋Š” ๋งŽ์€ ์–‘์˜ Task๋ฅผ ๋™์‹œ์— ์ƒ์„ฑํ•ด์„œ ํ•œ ๋ฒˆ์— ๋งŒ๋“ค์–ด์„œ ๋ฆฌํ„ด์‹œํ‚ค๊ณ ,
๋ฐ–์—์„œ์˜ for-in ๋ฃจํ”„๋Š” ์ˆœ์ฐจ์ ์œผ๋กœ thumbnail ๋”•์…”๋„ˆ๋ฆฌ๋ฅผ ์„ธํŒ…ํ•ฉ๋‹ˆ๋‹ค.

๋งŒ์•ฝ์— Error๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด async-let ์ฒ˜๋Ÿผ ์ข…๋ฃŒ๋˜์ง€ ์•Š๊ณ  cancel, waitํ•˜๋‹ค๊ฐ€ ํ•จ์ˆ˜ ์ข…๋ฃŒ๋  ๋•Œ ๊ฐ™์ด ๋๋‚œ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.


์—ฌ๊ธฐ๊นŒ์ง€ Scoped(๋ฒ”์œ„๊ฐ€ ์ง€์ •๋œ) Structured(๊ตฌ์กฐํ™”๋œ) Task๋ฅผ ์ œ๊ณตํ•˜๋Š”

  • async-let
  • group Task

๋ฐฉ๋ฒ•์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ unStructured Task ์ž…๋‹ˆ๋‹ค.
์™ ์ง€ ์ด๊ฒŒ ์ œ๊ฐ€ ์ฐพ๋˜ Task์ผ ๊ฒƒ ๊ฐ™์€ ์˜ˆ๊ฐ์ด ๋“ญ๋‹ˆ๋‹ค!


Unstructured Task

์•ฑ์„ ๊ตฌํ˜„ํ•˜๋ฉด์„œ.. Task๊ฐ€ ํ•ญ์ƒ ๋ช…ํ™•ํ•œ ๊ณ„์ธต ๊ตฌ์กฐ์— ์†ํ•œ์‹์œผ๋กœ๋งŒ ๊ตฌํ˜„๋˜์ง„ ์•Š์Šต๋‹ˆ๋‹ค.
์˜คํžˆ๋ ค ํ›จ์”ฌ ๋” ๋งŽ์€ ์ˆ˜๋™๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๋Œ€์‹  ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜๋Š” Unstructuredํ•œ ๋ฐฉ์‹์ด ๋” ๋งŽ์ฃ .

Not all tasks fit a structured pattern

์ œ์ผ ํ”ํ•œ ์ƒํ™ฉ์€,
non-async ์ฝ”๋“œ์—์„œ async ๊ณ„์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด Task๋ฅผ ์‹œ์ž‘ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ Parent Task๊ฐ€ ์ „ํ˜€ ์—†์„ ๊ฒฝ์šฐ ์ž…๋‹ˆ๋‹ค.

๋˜๋Š”,
๋‚ด๊ฐ€ ์›ํ•˜๋Š” Task์˜ ์ˆ˜๋ช…์ด single scope ๋˜๋Š” single function์ด ์•„๋‹์ˆ˜๋„ ์žˆ์ฃ .
์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐ์ฒด๋ฅผ active ์ƒํƒœ๋กœ ์ „ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์— ๋Œ€ํ•œ ์‘๋‹ต์œผ๋กœ Task๋ฅผ ์‹œ์ž‘ํ•œ ๋‹ค์Œ ๊ฐ์ฒด๋ฅผ deactivate ์ƒํƒœ๋กœ ์ „ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์— ๋Œ€ํ•œ ์‘๋‹ต์œผ๋กœ Task๋ฅผ ์ทจ์†Œํ•˜๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ AppKit ๋ฐ UIkit์—์„œ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ๊ฐ์ฒด๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ๋งŽ์ด ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ๋ฅผ ๋ณด์‹œ์ฃ !

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
        let ids = getThumbnailIds(for: item)
        let thumbnails = await fetchThumbnail2(for: ids)
    }

CollectionViewCell์— ๋„คํŠธ์›Œํฌ์—์„œ ๊ฐ€์ ธ์˜จ ์ธ๋„ค์ผ์„ ๊ฐ€์ ธ์™€ ๋„์šฐ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ Delegate ๋ฉ”์„œ๋“œ๋Š” async ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— async ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
MainThread์—์„œ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— awaitํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ด์ฃ  ใ…œ.ใ…œ


UI ์ž‘์—…์€ Main Thread์—์„œ ๋ฐœ์ƒํ•ด์•ผํ•˜๋ฉฐ, Swift๋Š” @MainActor๋ผ๊ณ  ํ‘œ๊ธฐํ•จ์œผ๋กœ์จ ์ด๊ฑธ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
๋งŒ์•ฝ์— ์šฐ๋ฆฌ๊ฐ€ Delegate ๋ฉ”์„œ๋“œ ์•ˆ์—์„œ Task ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค๋ฉด, ์šฐ๋ฆฌ๋Š” ์ด Task๊ฐ€ Main Actor์—์„œ ์‹คํ–‰๋˜๊ธธ ์›ํ•ฉ๋‹ˆ๋‹ค.
์ด ๋•Œ! unstructured Task๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
        let ids = getThumbnailIds(for: item)
        let thumbnails = await fetchThumbnail2(for: ids)
    }

์ด ์ฝ”๋“œ๋ฅผ unstructured Task๋ฅผ ์‚ฌ์šฉํ•ด async ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด๋ณผ๊ฒŒ์š”.

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
        let ids = getThumbnailIds(for: item)
        Task {
            let thumbnails = try await fetchThumbnail2(for: ids)
            display(thumbnails, in: cell)
        }
    }

async ๊ด€๋ จ ์ฒ˜๋ฆฌ๋ฅผ Task์•ˆ์— ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค.
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฌด์Šจ์ผ์ด ์ผ์–ด๋‚˜๋ƒ๋ฉด..

Task๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์‹œ์ ์— ๋„๋‹ฌํ•˜๋ฉด Swift๋Š” original scope์™€ ๋™์ผํ•œ Actor์—์„œ ์‹คํ–‰๋˜๋„๋ก ์Šค์ผ€์ค„๋ง ํ•ฉ๋‹ˆ๋‹ค. (Task๋Š” ์ž๋™์œผ๋กœ ์Šค์ผ€์ค„๋ง)
์ด ๊ฒฝ์šฐ์—๋Š” MainActor์—์„œ ์‹คํ–‰๋˜๊ฒ ์ฃ ?

์ธ๋„ค์ผ์„ ๋ฐ›์•„์˜ค๋Š” Task๋Š” Delegate ํ•จ์ˆ˜ ์•ˆ์—์„œ Main Thread๋ฅผ ์ฐจ๋‹จํ•˜์ง€ ์•Š๊ณ  ๊ธฐํšŒ๊ฐ€ ์žˆ์„ ๋•Œ Main Thread์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
์ด๋Ÿฐ์‹์œผ๋กœ Task๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๊ตฌ์กฐํ™”๋œ ์ฝ”๋“œ์™€ ๊ตฌ์กฐํ™” ๋˜์ง€ ์•Š์€ ์ฝ”๋“œ ์‚ฌ์ด์˜ ์ค‘๊ฐ„์ง€์ ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŠน์ง•

์ด๋ ‡๊ฒŒ ์ง์ ‘ ์ƒ์„ฑ๋œ Task์˜ ํŠน์ง•์ด ์žˆ๋Š”๋ฐ์š”.

  • ์ง์ ‘ ์ƒ์„ฑ๋œ Task๋Š” ์ด๋ฏธ ์‹œ์ž‘๋˜์–ด์žˆ๋Š” Actor๋ฅผ ์ƒ์†ํ•˜๋ฉฐ, Group Task, async-let๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ original Task์˜ ์šฐ์„ ์ˆœ์œ„ ๋ฐ ๊ธฐํƒ€ ํŠน์„ฑ๋„ ์ƒ์†ํ•ฉ๋‹ˆ๋‹ค.
  • Lifetime is not confined to any scope -> ๋ฒ”์œ„์— ์ƒ์†๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • Can be launched anywhere, even non-async functions. ์–ด๋””์„œ๋‚˜ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ฌ์ง€์–ด non-async ํ•จ์ˆ˜์—์„œ๋„์š”!
  • ํ•˜์ง€๋งŒ Structured Concurrency๊ฐ€ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์คฌ๋˜๊ฒƒ๋“ค์„ ์ˆ˜๋™์œผ๋กœ ๊ด€๋ฆฌํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
  • Must be manually cancelled or awaited
  • ์ทจ์†Œ ๋ฐ ์˜ค๋ฅ˜๋Š” ์ž๋™์œผ๋กœ ์ „ํŒŒ๋˜์ง€ ์•Š์œผ๋ฉฐ, ๋ช…์‹œ์ ์ธ ์กฐ์น˜๋ฅผ ์ทจํ•˜์ง€ ์•Š๋Š” ํ•œ ์ž์—… ๊ฒฐ๊ณผ๋ฅผ ์•”์‹œ์ ์œผ๋กœ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Must be manually cancelled or awaited

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
        let ids = getThumbnailIds(for: item)
        Task {
            let thumbnails = try await fetchThumbnail2(for: ids)
            display(thumbnails, in: cell)
        }
    }

๊ทธ๋ž˜์„œ!!!! ์ € ์œ„ ์˜ˆ์‹œ์—์„œ collectionView๋ฅผ ํ‘œ๊ธฐํ•  ๋•Œ ์ธ๋„ค์ผ์ด ๋‹ค์šด๋กœ๋“œ ๋˜๊ธฐ๋„์ „์— ์Šคํฌ๋กค๋˜๋ฉด ํ•ด๋‹น Task๋„ ์ทจ์†Œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ € Task๋Š” unscoped task์ด๊ธฐ ๋•Œ๋ฌธ์— Task cancel์€ ์ž๋™์ด ์•„๋‹™๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ..

    var thumbnailTasks: [IndexPath: Task<Void, Error>] = [:]
    
    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
        let ids = getThumbnailIds(for: item)
        thumbnailTasks[item] = Task {
            defer { thumbnailTasks[item] = nil } // defer๊ฐ€ ๋ญ์ง•
            let thumbnails = try await fetchThumbnail2(for: ids)
            display(thumbnails, in: cell)
        }
    }

์ด๋ ‡๊ฒŒ ์ €์žฅํ•ด๋’€๋‹ค๊ฐ€!!

    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        thumbnailTasks[indexPath]?.cancel()
    }

cancel ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


Detached Tasks

์œ„์—์„œ ์‚ดํŽด๋ณธ Task๋Š” ๋ชจ๋‘ Task๋Š” ๋ชจ๋‘ ์ƒ์„ฑ๋˜๋ฉด original scope์˜ ํŠน์„ฑ์„ ์ƒ์†ํ•˜๋Š” ํŠน์ง•์ด ์žˆ์—ˆ๋Š”๋ฐ์š”.
์•„๋ฌด๊ฒƒ๋„ ์ƒ์†ํ•˜๊ณ  ์‹ถ์ง€ ์•Š์€ Task๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
์™„์ „ํ•œ ์ž์œจ์„ฑ์„ ์œ„ํ•ด...

cancel, await๋ฅผ manuallyํ•˜๊ฒŒ ๊ด€๋ฆฌํ•ด์ค˜์•ผํ•˜๋ฉฐ lifeTime์ด unscopeํ•ฉ๋‹ˆ๋‹ค.
originating context์—์„œ ์•„๋ฌด๊ฒƒ๋„! ์ƒ์†ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์„œ๋ฒ„์—์„œ ์ธ๋„ค์ผ์„ ๊ฐ€์ ธ์˜จ ํ›„ ๋‚˜์ค‘์— ๊ฐ€์ ธ์˜ค๋ ค๊ณ  ํ•  ๋•Œ ๋„คํŠธ์›Œํฌ์— ๋‹ค์‹œ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š๋„๋ก ๋กœ์ปฌ ๋””์Šคํฌ ์บ์‹œ์— ๊ธฐ๋กํ•˜๋ ค๊ณ  ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.
์บ์‹ฑ์€ ๋ฉ”์ธ ์•กํ„ฐ์—์„œ ๋ฐœ์ƒํ•  ํ•„์š”๊ฐ€ ์—†์œผ๋ฉฐ ๋ชจ๋“  ์ธ๋„ค์ผ ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์ทจ์†Œํ•˜๋”๋ผ๋„ ๊ฐ€์ ธ์˜จ ์ธ๋„ค์ผ์„ ์บ์‹œํ•˜๋Š”๊ฒƒ์ด ์—ฌ์ „ํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt item: IndexPath) {
        let ids = getThumbnailIds(for: item)
        thumbnailTasks[item] = Task {
            defer { thumbnailTasks[item] = nil } // defer๊ฐ€ ๋ญ์ง•
            let thumbnails = try await fetchThumbnail2(for: ids)
            display(thumbnails, in: cell)
            
            /*
             Datached Task
             ๋กœ์ปฌ ๋””์Šคํฌ์— ์บ์‹ฑํ•˜๋Š”๊ฑฐ๋‹ˆ๊นŒ ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„์˜ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์— ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
             */
            Task.detached(.background, operation: {
                writeToLocalcache(thumbnails)
            })
        }
    }

์š”๋Ÿฐ์‹์œผ๋กœ Detach Task๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


Task ์ด ์ •๋ฆฌ


๋Š๋‚€์ 

๋“œ๋””์–ด..!!
๋“œ๋””์–ด ๋๋‚ฌ๋‹ค..!!!!
์ด 27๋ถ„ 54์ดˆ ์งœ๋ฆฌ ์˜์ƒ์„ 3์ผ ๋™์•ˆ ์ •๋ฆฌ?.. ๋ฐ›์•„ ์ ์—ˆ๋‹ค.

ํ‡ด๊ทผ ํ›„์— ์Šคํ„ฐ๋””๋ฅผ ํ•˜๋Š”๋ฐ...์™œ 10๋ถ„ ์ •๋ฆฌํ•˜๋ฉด 2์‹œ๊ฐ„ ์ง€๋‚˜์žˆ๊ณ  ๊ทธ๋Ÿฌ๋ƒ๊ณ  ใ… ใ… ใ… 
์ด์ œ์„œ์•ผ meet async/await ํ–ˆ๋Š”๋ฐ Unstructured Task ํ•˜๋‚˜ ์•Œ๋ ค๋‹ค๊ฐ€ ์ฒดํ• ๋ป”ํ–ˆ๋‹ค.

์–ด์จ‹๋“  ํฌ๊ธฐํ•˜์ง€ ์•Š์•˜์–ด...
์ด ์„ธ์…˜์€ async/await์— ๋Œ€ํ•œ ๊ณต๋ถ€๋ฅผ ๋” ํ•œ ๋‹ค์Œ์— ๋‹ค์‹œ ํ•œ ๋ฒˆ ๋ด์•ผ๊ฒ ๋‹ค.

์Œ ๊ฒฐ๋ก ์ ์œผ๋กœ ๋‚ด๊ฐ€ ๊ถ๊ธˆํ–ˆ๋˜ Task๋Š”
Unstructured task ์˜€๊ณ ,
์Œ..์ง€๊ธˆ์œผ๋กœ์จ๋Š” ๋‹ค๋ฅธ Task๋ฅผ ์“ธ ์ˆ˜ ์žˆ์„์ง€ ๋ชจ๋ฅด๊ฒ ๋‹ค.
์˜ˆ์ œ์ฝ”๋“œ๋ฅผ ์ข€ ๋” ํ•™์Šตํ•ด์•ผ ๋  ๊ฒƒ ๊ฐ™๋‹ค.

๋‹ค์Œ ํ•™์Šต์„ ํ•˜๊ธฐ์ „์—..
asyn/await์—์„œ ๋ฐฐ์šด ์˜ˆ์ œ๋“ค์„ ๋‹ค์‹œ ์ •๋ฆฌํ•˜๊ณ 
๋‚ด๊ฐ€ ์ง€๊ธˆ์“ฐ๋Š” ํ”„๋กœ์ ํŠธ์—์„œ ์ด๊ฑธ ์–ด๋–ป๊ฒŒ ๋…น์ผ ์ˆ˜ ์žˆ์„์ง€ ๊ณ ๋ฏผํ•ด๋ณด๊ณ ..!
alamofire์™€๋Š” ์–ด๋–ป๊ฒŒ ํ•ฉ์ณ์งˆ์ง€ ํ•™์Šตํ•ด๋ด์•ผ๊ฒ ๋‹ค.

Actor, defer์˜ ๊ฐœ๋…๋„.
๋!

profile
https://hyunndyblog.tistory.com/163 ํ‹ฐ์Šคํ† ๋ฆฌ์—์„œ ์ด์‚ฌ ์ค‘

0๊ฐœ์˜ ๋Œ“๊ธ€