๐ŸŒ™

[Swift] Swift Concurrency ์™„์ „ ์ •๋ณต

yeggrrr๐Ÿผ 2025. 11. 11. 08:52
728x90

 


async/await๋ถ€ํ„ฐ actor๊นŒ์ง€, ๋™์‹œ์„ฑ์˜ ์ง„์งœ ๋™์ž‘ ์›๋ฆฌ

Swift 5.5 ์ดํ›„ ๋“ฑ์žฅํ•œ Concurrency(๋™์‹œ์„ฑ)๋Š” iOS ๊ฐœ๋ฐœ์˜ ํŒจ๋Ÿฌ๋‹ค์ž„์„ ์™„์ „ํžˆ ๋ฐ”๊ฟ”๋†“์•˜๋‹ค.
์ด์ „์—๋Š” ์ฝœ๋ฐฑ, completion handler, GCD(DispatchQueue)๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์ œ์–ดํ–ˆ์ง€๋งŒ,
์ด์ œ๋Š” ์–ธ์–ด ์ˆ˜์ค€์—์„œ ์•ˆ์ „ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋น„๋™๊ธฐ๋ฅผ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
์ด๋ฒˆ ๊ธ€์—์„œ๋Š” async/await, Task, actor๋ฅผ ์ค‘์‹ฌ์œผ๋กœ
"Swift ๋™์‹œ์„ฑ์ด ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”๊ฐ€"๋ฅผ ์‹ค๋ฌด ๊ฐœ๋ฐœ์ž ๊ด€์ ์—์„œ ์ •๋ฆฌํ•˜๋ ค๊ณ  ํ•œ๋‹ค.
 

์™œ Concurrency์ธ๊ฐ€?

๊ธฐ์กด GCD ๊ธฐ๋ฐ˜ ์ฝ”๋“œ์˜ ๋ฌธ์ œ์ ์€ ๋ช…ํ™•ํ•˜๋‹ค.

DispatchQueue.global().async {
    let data = fetchData()
    DispatchQueue.main.async {
        self.label.text = data.title
    }
}

- ์ฝœ๋ฐฑ ์ค‘์ฒฉ์œผ๋กœ ์ธํ•œ ๊ฐ€๋…์„ฑ ์ €ํ•˜
- ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ ๋ณด์žฅ ๋ถ€์žฌ
- ์ฝ”๋“œ ํ๋ฆ„์ด "์ˆœ์ฐจ์ "์ด์ง€ ์•Š์•„ ๋””๋ฒ„๊น… ์–ด๋ ค์›€
Swift Concurrency๋Š” ์ด๋ฅผ ์ปดํŒŒ์ผ๋Ÿฌ ์ˆ˜์ค€์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์—ˆ๋‹ค.
๋น„๋™๊ธฐ ํ•จ์ˆ˜ ๊ฐ„์˜ ํ˜ธ์ถœ ๊ด€๊ณ„๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ‘œํ˜„ํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ ๊ฒฝ์Ÿ(Data Race)์„ ์–ธ์–ด ์ฐจ์›์—์„œ ๋ฐฉ์ง€ํ•œ๋‹ค.
 

async/await ๊ธฐ๋ณธ ๊ตฌ์กฐ
func fetchUser() async throws -> User {
    try await NetworkManager.shared.loadUser()
}

- async: ํ•จ์ˆ˜๊ฐ€ ๋น„๋™๊ธฐ๋กœ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ์Œ์„ ๋ช…์‹œ
- await: ํ˜ธ์ถœ ์‹œ์ ์—์„œ ์ผ์‹œ ์ค‘์ง€(suspend) ๊ฐ€๋Šฅ์„ฑ์„ ๋‚˜ํƒ€๋ƒ„

์ฆ‰, await๋Š” ์ž ๊น ๋ฉˆ์ท„๋‹ค๊ฐ€ ๊ฒฐ๊ณผ๊ฐ€ ์ค€๋น„๋˜๋ฉด์„œ ๋‹ค์‹œ ์ด์–ด์ง€๋Š” ๊ฒƒ์ด๋‹ค.
Swift๋Š” ์ด ์ค‘๋‹จ ์ง€์ ์„ ์ž๋™ ๊ด€๋ฆฌํ•˜๋ฏ€๋กœ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ฝœ๋ฐฑ์„ ์ง์ ‘ ๋‹ค๋ฃฐ ํ•„์š”๊ฐ€ ์—†๋‹ค.
 

Task์™€ Structured Concurrency

Swift์˜ Task๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์˜ ๊ธฐ๋ณธ ๋‹จ์œ„์ด๋‹ค.

Task {
    let data = try await fetchData()
    print(data)
}

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฐœ๋…์€ Structured Concurrency์ด๋‹ค.
๋ถ€๋ชจ Task๊ฐ€ ์ทจ์†Œ๋˜๋ฉด ํ•˜์œ„ Task๋“ค๋„ ์ž๋™์œผ๋กœ ์ทจ์†Œ๋˜๋ฉฐ, ๋ˆ„์ˆ˜ ์—†๋Š” ๊ตฌ์กฐ๊ฐ€ ๋œ๋‹ค.

[์˜ˆ์‹œ] - ์—ฌ๋Ÿฌ ์ž‘์—… ๋ณ‘๋ ฌ ์‹คํ–‰

await withTaskGroup(of: Int.self) { group in
    for i in 1...5 {
        group.addTask {
            return await heavyJob(i)
        }
    }
    let total = await group.reduce(0, +)
    print("Total:", total)
}

TaskGroup์€ ์ž์‹ ์ž‘์—…๋“ค์„ ๋ฌถ์–ด ๋ณ‘๋ ฌ ์‹คํ–‰ํ•˜๋ฉฐ, ๋‚ด๋ถ€์ ์œผ๋กœ suspend/resume๋ฅผ ํ†ตํ•ด ์ž๋™ ์Šค์ผ€์ค„๋ง ๋œ๋‹ค.
 

actor๋กœ ๋ฐ์ดํ„ฐ ๊ฒฝ์Ÿ ๋ฐฉ์ง€ํ•˜๊ธฐ

ํด๋ž˜์Šค๋Š” ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ๋™์‹œ์— ์ ‘๊ทผํ•˜๋ฉด ์œ„ํ—˜ํ•˜์ง€๋งŒ,
actor๋Š” ๋‚ด๋ถ€ ์ƒํƒœ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ง๋ ฌํ™”(serialize)ํ•œ๋‹ค.

actor Counter {
    private var value = 0
    
    func increment() { value += 1 }
    func getValue() -> Int { value }
}

- actor ๋‚ด๋ถ€์˜ value๋Š” ์˜ค์ง ํ•˜๋‚˜์˜ Task๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
- ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ๋•Œ๋Š” ํ•ญ์ƒ await ํ•„์š”

let counter = Counter()
await counter.increment()
print(await counter.getValue())

์ฆ‰, actor๋Š” "๋น„๋™๊ธฐ ์•ˆ์ „ ํด๋ž˜์Šค"๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
 

@MainActor๋กœ UI ์•ˆ์ „ํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธ

UI๋Š” ํ•ญ์ƒ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ๋งŒ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค.
@MainActor๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น ํ•จ์ˆ˜๋‚˜ ํ”„๋กœํผํ‹ฐ๊ฐ€ ํ•ญ์ƒ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋จ์„ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ณด์žฅํ•œ๋‹ค.

@MainActor
func updateUI() {
    label.text = "์—…๋ฐ์ดํŠธ ์™„๋ฃŒ"
}

์˜ˆ์ „์—๋Š” DispatchQueue.main.async๋กœ ๊ฐ์ŒŒ๋˜ ๋ถ€๋ถ„์„ 
@MainActor๋กœ ์„ ์–ธ๋งŒ ํ•ด๋„ ๋™์ผํ•œ ํšจ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.
 

์‹ค๋ฌด์—์„œ ์ž์ฃผ ์“ฐ์ด๋Š” ํŒจํ„ด: Task ์ทจ์†Œ

API ์š”์ฒญ์ด ์—ฐ์†์ ์œผ๋กœ ๋“ค์–ด์˜ฌ ๋•Œ, ์ด์ „ ์š”์ฒญ์„ ์ทจ์†Œํ•˜๊ณ  ์ƒˆ๋กœ์šด ์š”์ฒญ๋งŒ ์œ ์ง€ํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ๋งŽ๋‹ค.

var currentTask: Task<Void, Never>?

func loadData() {
    currentTask?.cancel()
    currentTask = Task {
        do {
            let data = try await fetchData()
            await updateUI(with: data)
        } catch {
            if Task.isCancelled { return }
        }
    }
}

์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ์ด์ „ ๋น„๋™๊ธฐ ์ž‘์—…์˜ ์ทจ์†Œ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
Task ์ทจ์†Œ๋Š” Swift Concurrency์˜ ๊ตฌ์กฐ์  ํŠน์„ฑ ๋•๋ถ„์— ๊ฐ„๋‹จํ•˜๊ฒŒ ์ œ์–ด๋œ๋‹ค.
 


 

Swift์˜ Concurrency๋Š” ๋‹จ์ˆœํžˆ ๋ฌธ๋ฒ•์ด ์•„๋‹ˆ๋ผ, ์•ˆ์ „ํ•œ ๋™์‹œ์„ฑ ๋ชจ๋ธ์ด๋‹ค.
์ด์ œ๋Š” ๋น„๋™๊ธฐ๋ฅผ "์Šค๋ ˆ๋“œ ๋‹จ์œ„"๊ฐ€ ์•„๋‹Œ "์ž‘์—…(Task) ๋‹จ์œ„"๋กœ ๋‹ค๋ฃจ๋Š” ์‹œ๋Œ€์ด๋ฉฐ,
์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ฐ์ดํ„ฐ ๊ฒฝ์Ÿ์„ ์‚ฌ์ „์— ๋ง‰์•„์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.
์‹ค๋ฌด์—์„œ๋Š” ์ด ํŒจํ„ด๋“ค์„ ์กฐํ•ฉํ•ด์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.
- API ์—ฐ์† ํ˜ธ์ถœ ๊ด€๋ฆฌ
- ViewModel์˜ async ์ฒ˜๋ฆฌ
- actor ๊ธฐ๋ฐ˜ ์ƒํƒœ ์ €์žฅ์†Œ ๊ตฌ์„ฑ

Swift์—์„œ ๋™์‹œ์„ฑ์€ ๋” ์ด์ƒ ์„ ํƒ์ด ์•„๋‹Œ ๊ธฐ๋ณธ๊ธฐ๊ฐ€ ๋œ ๊ฒƒ ๊ฐ™๋‹ค.
๊ธฐ์กด์— GCD๋‚˜ OperationQueue ๊ธฐ๋ฐ˜ ์ฝ”๋“œ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๋Œ€์‹ ,
์ด์ œ๋Š” async/await์™€ actor๋กœ ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ์ด ์œ ์ง€๋ณด์ˆ˜์™€ ์•ˆ์ „์„ฑ ์ธก๋ฉด์—์„œ ํ›จ์”ฌ ํšจ์œจ์ ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.
์ƒˆ๋กœ์šด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๋งˆ๋‹ค "์ด ์ž‘์—…์€ ์–ด๋–ค Task๋กœ, ์–ด๋–ค Actor์•ˆ์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋ ๊นŒ?"๋ฅผ ์ƒ๊ฐํ•˜๋Š” ์Šต๊ด€์ด Swift Concurrency๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ํ™œ์šฉํ•˜๋Š” ์ฒซ ๊ฑธ์Œ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์ด์ „์—๋Š” escaping ํด๋กœ์ €์™€ completion handler๋กœ ๋น„๋™๊ธฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ ๋ณต์žกํ•œ ์ฝœ๋ฐฑ ์ฒด์ธ์€ ๊ฐ์ˆ˜ํ•ด์•ผํ–ˆ์—ˆ๋‹ค.
๊ทธ ์ด์œ ๋Š” ๋‹น์‹œ์—” Swift Concurreny๊ฐ€ ๋‚ฏ์„ค์—ˆ๊ณ , ๋ฐฐ์›Œ์•ผ ํ•  ๊ฐœ๋…์ด ๋„ˆ๋ฌด ๋งŽ์•„ ์‰ฝ๊ฒŒ ๋‹ค๊ฐ€์„œ์ง€ ๋ชปํ–ˆ๋‹ค.
'async/await๊ฐ€ ๋ญ”๋ฐ? ์–ด๋ ต๋‹ค. ๊ทธ๋ƒฅ ์•„๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ฐœ๋ฐœํ•˜์ž'๋ผ๋Š” ์ƒ๊ฐ์ด ์†”์งํ•œ ๋งˆ์Œ์ด์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ ์ ์šฉํ•ด๋ณด๋‹ˆ, ๋ณต์žกํ–ˆ๋˜ ๋น„๋™๊ธฐ ํ๋ฆ„์ด ๋†€๋ผ์šธ ์ •๋„๋กœ ๋‹จ์ˆœํ•ด์ง€๊ณ  ์ฝ”๋“œ์˜ ์˜๋„๊ฐ€ ๋ช…ํ™•ํ•ด์กŒ๋‹ค.
์ง€๊ธˆ ๋Œ์ด์ผœ๋ณด๋ฉด, Swift Concurrency๋ฅผ ์ดํ•ดํ•˜๋ ค๋Š” ๊ณผ์ •์ด ๋‹จ์ˆœํ•œ ๋ฌธ๋ฒ• ํ•™์Šต์ด ์•„๋‹ˆ๋ผ
๋น„๋™๊ธฐ๋ผ๋Š” ๊ฐœ๋… ์ž์ฒด๋ฅผ ์ƒˆ๋กญ๊ฒŒ ๋ฐ›์•„๋“ค์ด๋Š” ๊ฒฝํ—˜์ด์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

๋ณต์žกํ•œ ์ฝ”๋“œ๋ฅผ ์ค„์ด๋Š” ๋ฐ์„œ ๊ทธ์น˜์ง€ ์•Š๊ณ , '์–ด๋–ป๊ฒŒ ๋” ์•ˆ์ „ํ•œ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ธ๊ฐ€'๋ฅผ ์ƒ๊ฐํ•˜๊ฒŒ ๋˜๋ฉด์„œ
์•ฑ์˜ ์ „์ฒด ๊ตฌ์กฐ๋ฅผ ๋‹ค์‹œ ๋ฐ”๋ผ๋ณด๊ฒŒ ๋˜์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.
์ดํ›„์— ๋ธ”๋กœ๊ทธ์—์„œ ์ด ๊ตฌ์กฐ๋ฅผ ViewModel๊ณผ ๋„คํŠธ์›Œํฌ ๊ณ„์ธต์— ์–ด๋–ป๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ์„์ง€ ์ž‘์„ฑํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.
(๋‹ค์Œ ๊ธ€์€ ์•„๋‹˜. ์•„๋ฌดํŠผ ์“ธ ์˜ˆ์ •.)
 


 

728x90