π WWDC21 : Explore structured concurrency in Swift
Structured Programming

- ꡬ쑰ν λ νλ‘κ·Έλλ°
- μ μ λ²μλ₯Ό μ¬μ©
- ν΄λΉ λ²μ λ΄μμλ§ λ³μ μ¬μ© κ°λ₯
- 쑰건λΆλ‘λ§ μ½λκ° μ€νλ¨
- λ²μ λ²μ΄λλ©΄ μ’ λ£
- μ μ΄νλ¦μ λ³΄λ€ μ§κ΄μ μΌλ‘ μ μ μμ
- νλ‘κ·Έλ¨μ μμμ μλλ‘ μ½μ μ μμ
- μ μ λ²μλ₯Ό μ¬μ©
Completion Example
func fetchThumbnails(
for ids: [String],
completion handler: @escaping ([String: UIImage]?, Error?) -> Void
) {
guard let id = ids.first else { return handler([:], nil) }
let request = thumbnailURLRequest(for: id)
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
// μλ¬ νΈλ€λ§ λΆκ°
// - νλμ ν¨μκ° μλ ν¨μμμ λ°μν μ€λ₯λ§ μ²λ¦¬νλ κ²μ΄ μλ―ΈμκΈ°λλ¬Έ
guard let response = response,
let data = data
else {
return handler(nil, error)
}
// ... check response ...
UIImage(data: data)?.prepareThumbnail(of: thumbSize) { image in
guard let image = image else {
return handler(nil, ThumbnailFailedError())
}
// ν΄λΉ ν¨ν΄ μ¬μ© μ, 루νλ₯Ό μ¬μ©νμ¬ κ° μΈλ€μΌμ μ²λ¦¬ν μ μμ
// - ν¨μκ° μλ£λ ν μ€νλλ μ½λκ° νΈλ€λ¬ λ΄μ μ€μ²©λΌμΌνλ―λ‘ μ¬κ·κ° νμν¨
fetchThumbnails(for: Array(ids.dropFirst())) { thumbnails, error in
// ... add image to thumbnails ...
}
}
}
dataTask.resume()
}
Async/await Example
// μ’μ보μ΄μ§λ§ μμ² κ°μ μ΄λ―Έμ§μ λν΄ μΈλ€μΌμ μμ±νλ€λ©΄?
// - κ° μΈλ€μΌμ ν λ²μ νλμ© μ²λ¦¬νλ κ²μ΄ μ’μ보μ΄μ§ μμ
// - λν κ³ μ λ ν¬κΈ°λ‘ λ°μμ€μ§ μλλ€λ©΄..?
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
var thumbnails: [String: UIImage] = [:]
for id in ids {
let request = thumbnailURLRequest(for: id)
let (data, response) = try await URLSession.shared.data(for: request)
try validateResponse(response)
guard let image = await UIImage(data: data)?.byPreparingThumbnail(ofSize: thumbSize) else {
throw ThumbnailFailedError()
}
thumbnails[id] = image
}
return thumbnails
}
- Task
- λΉλκΈ°λ₯Ό μ¬μ©ν μ μκ²νλ Swiftμ μλ‘μ΄ κΈ°λ₯
- μμ νκ³ ν¨μ¨μ μΌ λ λ³λ ¬λ‘ μ€νλλλ‘ ν¨
- λΉλκΈ° ν¨μλ₯Ό νΈμΆν΄λ νΈμΆμ λν μλ‘μ΄ μμ μ΄ μμ±λμ§ μμ
Async-let

- μΌλ°μ μΈ let λ°μΈλ©
- νλμ μ€ννλ¦
- let result = URLSession.shared.data(…)
- λ°μ΄ν°λ₯Ό λ€μ΄λ°μμ€λ λμ λ€λ₯Έ μμ
μ ν μ μκΈ°λ₯Ό λ°λ
- let μμ asyncλ₯Ό λͺ μ
- λ°μ΄ν°λ₯Ό λ€μ΄λ°μμ€λ λμ λ€λ₯Έ μμ
μ ν μ μκΈ°λ₯Ό λ°λ

- async-let λ°μΈλ©
- async letμ λ§λκΈ° μ μλ‘μ΄ νμ μμ
μ μμ±
- λͺ¨λ μμ
μ νλ‘κ·Έλ¨μ μ€νμ λνλ΄κΈ° λλ¬Έμ ν΄λΉ λ¨κ³μμ νλ¦μ λν λ°©ν₯μ΄ λ κ°μ§λ‘ λλ¨
- URLSession.shared.data(…) : λ°μ΄ν° λ€μ΄λ‘λλ₯Ό μν νμ μμ
- result : λ³μμ κ²°κ³Όκ°μ λ°μΈλ©νλ μμ μμ
- λͺ¨λ μμ
μ νλ‘κ·Έλ¨μ μ€νμ λνλ΄κΈ° λλ¬Έμ ν΄λΉ λ¨κ³μμ νλ¦μ λν λ°©ν₯μ΄ λ κ°μ§λ‘ λλ¨
- μμ μμ μμλ νμ μμ μ΄ λ°μ΄ν°λ₯Ό λ°μμ€λλμ μ΄ν μμ μ μνν¨
- κ²°κ³Όκ°μ΄ νμν μ½λμ λλ¬νκ² λλ©΄ μμ μμ
(result)λ νμ μμ
(λ°μ΄ν° λ€μ΄λ‘λ)κ° μλ£λ λκΉμ§ κΈ°λ€λ¦Ό
- URLSessionμμ μλ¬κ° λ°μν κ°λ₯μ±μ΄ μμ ⇒ try
- κ²°κ³Όκ°μ λ€μ μ½λλ€νμ¬ λΉλκΈ° μμ μ΄ λ μΌμ΄λμ§ μμ
Task Tree Example

- μμ
νΈλ¦¬λΌλ κ³μΈ΅ ꡬ쑰μ μΌλΆ
- μμ μ μ·¨μ(cancellation), μμ μ μ°μ μμ, μμ λ΄ μ§μλ³μ κ°μ κ²λ€μ μν₯μ λ―ΈμΉ¨
- νλμ λΉλκΈ° ν¨μμμ λ€λ₯Έ ν¨μλ‘ νΈμΆν λλ§λ€ λμΌν μμ μ΄ νΈμΆμ μ€ννλλ° μ¬μ©λ¨
- μλ‘μ΄ κ΅¬μ‘°ν λ μμ
μμ± μ, νμ¬ ν¨μκ° μ€ν μ€μΈ μμ
μ νμ νλͺ©μ΄ λ¨
- μμ μ νΉμ κΈ°λ₯μ νμ νλͺ©μ΄ μλμ§λ§ ν΄λΉ κΈ°λ₯μ μλͺ λ²μλ ν΄λΉ κΈ°λ₯μΌλ‘ μ νλ μ μμ
- νΈλ¦¬λ κ° μμ μμ
κ³Ό ν΄λΉ νμ μμ
κ°μ λ§ν¬λ‘ ꡬμ±λ¨
- λ§ν¬λ λͺ¨λ νμμμ
μ΄ μλ£λ μ΄νμλ§ μμ μμ
μ΄ μλ£λ μ μλ€λ κ·μΉμ μ μ©ν¨
- μ΄ κ·μΉμ νμμμ μ΄ λκΈ°λλ κ²μ λ°©μ§νλ λΉμ μμ μ μ΄ νλ¦μλ λΆκ΅¬νκ³ μ μ©λ¨
- λ§ν¬λ λͺ¨λ νμμμ
μ΄ μλ£λ μ΄νμλ§ μμ μμ
μ΄ μλ£λ μ μλ€λ κ·μΉμ μ μ©ν¨
- κ²°κ΅ νμμμ λ€μ΄ λͺ¨λ μλ£λμ΄μΌ μ΅μμ μμ μ΄ μ΅μ’ μ μΌλ‘ μ’ λ£λ¨
- μμ μλͺ μ κ΄λ¦¬νλλ° λμμ μ€ μ€μλ‘ μμ μ΄ λ©λͺ¨λ¦¬ λμλ₯Ό λ°©μ§ν΄μ€
func fetchOneThumbnail(withID id: String) async throws -> UIImage {
let imageReq = imageRequest(for: id), metadataReq = metadataRequest(for: id)
// async : λ μμ
μ΄ λμμ μΌμ΄λκΈ° μν΄
// -> λ€μ΄λ‘λ μμ
μ΄ νμμμ μΌμ΄λλ―λ‘ try awaitμ μ°μ§μμ
// -> μ΄λ¬ν κ²°κ³Όλ μμ μμ
(data, metadata)μμλ§ κ΄μ°°λ¨
// μ΄λ―Έμ§ λ°μμ€κΈ°
//let (data, _) = try await URLSession.shared.data(for: imageReq)
async let (data, _) = URLSession.shared.data(for: imageReq)
// λ©νλ°μ΄ν° λ°μμ€κΈ°
// let (metadata, _) = try await URLSession.shared.data(for: metadataReq)
async let (metadata, _) = URLSession.shared.data(for: metadataReq)
// μλ¬μ λν κ²°κ³Όκ°λ metadata, dataμμ κ΄μ°°λκΈ° λλ¬Έμ ν΄λΉ λ°μ΄ν°λ₯Ό μ¬μ©ν λ try awaitμ μ¬μ©
// - μ΄λ―Έμ§ μμ
μ΄μ μ λ©νλ°μ΄ν° μμ
μ μ°μ μ²λ¦¬ -> μ€λ₯ λ°μ μ ν¨μ λ°λ‘ μ’
λ£μμΌμΌν¨
// -> κ·ΈλΌ λλ²μ§Έ μμ
μ? -> λλ²μ§Έ μμ
μ΄ μ·¨μλλ κ²μ΄ μλ ν΄λΉ κ²°κ³Όκ° λλ νμμμμ μλ¦Ό
// -> νμμμ
λ€μ΄ μλμΌλ‘ μ·¨μλ¨
guard let size = parseSize(from: try await metadata),
let image = try await UIImage(data: data)?.byPreparingThumbnail(ofSize: size)
else {
throw ThumbnailFailedError()
}
return image
}
Cancellation is Cooperative
- μ½λμμ λͺ
μμ μΌλ‘ μ·¨μλ₯Ό νμΈνκ³ μ μ ν λ°©μμΌλ‘ μ·¨μν΄μΌν¨
- λΉλκΈ° μ¬λΆμ κ΄κ²μμ΄ λͺ¨λ κΈ°λ₯μμ νμμ μ μ·¨μ μνλ₯Ό νμΈν μ μμ
- νΉν μ₯κΈ°μ μΈ μμ
μ΄ ν¬ν¨λ κ²½μ°, μ·¨μλ₯Ό μΌλν΄λμ΄μΌν¨
- λΆλΆμ μΈ κ²°κ³Όκ° λ°νλ μ μμμ λͺ μν΄μΌν¨
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
var thumbnails: [String: UIImage] = [:]
for id in ids {
// μ·¨μλ κ²½μ° μλ¬ λμ§κΈ°
try Task.checkCancellation()
// μ·¨μ μνλ₯Ό νμΈν΄μ μ²λ¦¬ν΄μ€ μλ μμ
// if Task.isCancelled { break }
thumbnails[id] = try await fetchOneThumbnail(withID: id)
}
return thumbnails
}
Group Tasks
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
var thumbnails: [String: UIImage] = [:]
for id in ids {
// λ§μ½ λͺ¨λ μΈλ€μΌμ κ°μ Έμ€λ μμ
μ λμμ νκ³ μΆλ€λ©΄ ? => Group
thumbnails[id] = try await fetchOneThumbnail(withID: id)
}
return thumbnails
}
func fetchOneThumbnail(withID id: String) async throws -> UIImage {
// ...
// λ κ°μ νμμμ
// async let : μμ
λ²μκ° μ§μ λ¨ = λ€μ 루ν λ°λ³΅μ΄ μμλκΈ° μ λ νμ μμ
μ΄ μλ£λΌμΌν¨
async let (data, _) = URLSession.shared.data(for: imageReq)
async let (metadata, _) = URLSession.shared.data(for: metadataReq)
// ...
}
- withThrowingTaskGroup
- μλ¬λ₯Ό λμ§λ μμ λ€μ λ¬Άμ
- κ·Έλ£Ήμ μΆκ°λ μμ μ κ·Έλ£Ήμ΄ μ μλ λ²μλ³΄λ€ μ€λ μ§μλ μ μμ
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
var thumbnails: [String: UIImage] = [:]
try await withThrowingTaskGroup(of: Void.self) { group in
for id in ids {
// κ·Έλ£Ήμ νμ μμ
μ μμ±
// νμμμ
μ΄ μ¦μ μμμ μμλ‘ μ€νλκΈ° μμ
// κ·Έλ£Ή κ°μ²΄κ° λ²μλ₯Ό λ²μ΄λλ©΄ κ·Έ μμ μλ λͺ¨λ μμ
μ μλ£κ° λκΈ° μνλ‘ λ¨
group.async {
// μ¬κΈ°μλ λ metadata, data νμ μμ
μμ±
// Data Race Error
// Error: Mutation of captured var 'thumbnails' in concurrently executing code
thumbnails[id] = try await fetchOneThumbnail(withID: id)
}
}
}
return thumbnails
}
- Data Raceλ₯Ό λ°©μ§νκΈ° μν΄ νμ μμ μ΄ κ°μ λ°ννλλ‘ λ³κ²½
func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
var thumbnails: [String: UIImage] = [:]
// withTrowingTaskGroupμ body
try await withThrowingTaskGroup(of: (String, UIImage).self) { group in
for id in ids {
group.async {
// μμ μμ
μ κ²°κ³Ό μ²λ¦¬μ λν μ μ μΈ μ±
μ λΆμ¬λ₯Ό μν΄ νμ μμ
κ²°κ³Ό λ°ν
// Key - Value
return (id, try await fetchOneThumbnail(withID: id))
}
}
// νμ
μ΄ AsyncSequence νλ‘ν μ½μ μ±ννλ κ²½μ°, for-awaitμ ν΅ν΄ λ°λ³΅ κ°λ₯
// νμ μμ
μ κ²°κ³Όλ₯Ό λ°λ³΅νλ©΄μ κ²°κ³Ό μ²λ¦¬
// Obtain results from the child tasks, sequentially, in order of completion.
for try await (id, thumbnail) in group {
thumbnails[id] = thumbnail
}
}
return thumbnails
}
- Group Taskλ ꡬ쑰ν λ λμμ±μ ν νν
- async-letκ³Ό κ°μ μμ
νΈλ¦¬μμλ μ½κ°μ μ°¨μ΄κ° μμ μ μμ
- μλ‘, κ·Έλ£Ή λ΄ νμ μμ
μμ μλ¬κ° λ°νλμμ λ κ·Έλ£Ή μ½λμ μ’
λ£λ₯Ό ν΅ν΄ λ²μλ₯Ό λ²μ΄λ λ λ°μν¨
- μ·¨μκ° μμμ μΌλ‘ λ°μνμ§μκ³ μ€μ§ λκΈ°λ§ μΌμ΄λ¨
- μλ‘, κ·Έλ£Ή λ΄ νμ μμ
μμ μλ¬κ° λ°νλμμ λ κ·Έλ£Ή μ½λμ μ’
λ£λ₯Ό ν΅ν΄ λ²μλ₯Ό λ²μ΄λ λ λ°μν¨
- async-letκ³Ό κ°μ μμ
νΈλ¦¬μμλ μ½κ°μ μ°¨μ΄κ° μμ μ μμ
- κ·Έλ£Ήμ cancelAll μ ν΅ν΄ κ·Έλ£Ή λ΄ λͺ¨λ μμ
μ μλμΌλ‘ μ·¨μ κ°λ₯
- μ·¨μ λ°©λ²μ μκ΄μμ΄ μ·¨μ μλ¦Όμ νμ μμ μΌλ‘ μλ €μ§
Unstructured Tasks
- λͺ
νν νΈλ¦¬ κ΅¬μ‘°κ° μλ κ΅¬μ‘°κ° μμ μ μμ
- λκΈ° μ½λ λ΄ λΉλκΈ° μν μ, μμ μμ μ΄ μλ κ²½μ°
- μνλ μμ μ μλͺ μ΄ λ¨μΌ λ²μκ±°λ λ¨μΌ ν¨μμ λ²μμ λ§μ§μμ λ
- μμ μ μμνλ κ°μ²΄κ° λ€λ₯Έ λ©μλμ λν κ²°κ³Όλ‘ μμ μ·¨μ κ°λ₯
- UICollectionViewDelegate λ΄ λ©μλμμ λΉλκΈ° μμ
μ μννλ €κ³ νλ€λ©΄
- Main Threadμμ λμνλ μμ
λ΄ λΉλκΈ° μμ
μ΄ λ€μ΄μμ΄ λΆκ°λ₯ν¨
- Taskλ₯Ό ν΅ν΄ κ°μΈμ€μ μμ
- κ·Έλ£Ή μμ μ΄λ async-letμ²λΌ μλ³Έμμ μ μ°μ μμ λ° κΈ°ν νΉμ±μ μμλ°μ
- μμ μ μλͺ μ μ§μ λμ§μμ
- μ·¨μλ λκΈ° μνλ₯Ό μ§μ κ΄λ¦¬ν΄μ£Όμ΄μΌν¨
- Taskλ₯Ό ν΅ν΄ κ°μΈμ€μ μμ
- Main Threadμμ λμνλ μμ
λ΄ λΉλκΈ° μμ
μ΄ λ€μ΄μμ΄ λΆκ°λ₯ν¨
- μ·¨μλ₯Ό μν μ½λ μμ

- νλ©΄μ΄ μ¬λΌμ§λ€λ©΄ μμ μ·¨μ

Detached Tasks
- μλͺ μ΄ μμ μμ μ μλͺ μΌλ‘ μ νλμ§ μμ
- μλ μμ
μΌλ‘λΆν° μ΄λ ν κ²λ μμλ°μ§ μμ
- μ΄λ€ μ€λ λμμ μΌμ΄λλ , μ΄λ€ μ°μ μμλ‘ μμ νλ μκ΄λ¬΄
- λ§€κ°λ³μλ₯Ό ν΅ν΄ μ°μ μμ λ±μ μ§μ ν΄μ€ μ μμ
- detached(priority:operation:)
- Runs the given throwing operation asynchronously as part of a new top-level task.
- = μλ‘μ΄ μ΅μμ μμ
μ μΌνμΌλ‘ λΉλκΈ° μμ
μν
- κ·Έλμ λ€λ₯Έ μμ μμ μ΄λ ν κ²λ μμλ°μ§ μκ³ μλͺ μ μ΄μ΄λ°μ§ μλκ² μλκΉ μΆμ

μ΄λ―Έμ§ μΊμ±
- μμ
μ΄ μ¬λ¬ κ°μΈ κ²½μ°
- Task.detachedνλλ§ μ·¨μν΄μ£Όλ©΄ νμκ° λͺ¨λ μ·¨μλλ―λ‘ νΈλ¦¬νκ² λ€μν λ°±κ·ΈλΌμ΄λ μμ μ ν μ μμ

Summary

'Study' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
| π Closure (0) | 2025.06.02 |
|---|---|
| π Concurrency Docs λ΄ μΌλΆ (0) | 2024.09.11 |
| π Continuation μμ보기 (0) | 2024.08.24 |
| π WWDC21: Meet async/await in Swift (0) | 2024.08.20 |
| π ARC Docs μ 리 (0) | 2024.08.06 |