1. Asynchronous and Parallel πŸ‘©β€πŸ’»

Swift λŠ” κ΅¬μ‘°ν™”λœ λ°©λ²•μœΌλ‘œ Asynchronous, Parallel μ½”λ“œ μž‘μ„±μ„ μ§€μ›ν•œλ‹€.

  • Asynchronous code λŠ” Single Thread둜 μž‘λ™ν•΄ ν•œ λ²ˆμ— ν•˜λ‚˜μ˜ μ½”λ“œλ§Œ 싀행이 κ°€λŠ₯ν•˜μ§€λ§Œ, μ½”λ“œλ₯Ό μž μ‹œ 쀑단 ν›„ λ‹€μ‹œ μž¬κ°œν•  수 μžˆλŠ” μ½”λ“œ λΈ”λŸ­μœΌλ‘œ, Fetching data λ˜λŠ” Parsing files 와 같은 long-running background task을 μš”μ²­ ν›„ κΈ°λ‹€λ¦¬λŠ” λ™μ•ˆ UI Update와 같은 short-term을 μˆ˜ν–‰ν•  수 μžˆλ‹€.
  • Parallel code λŠ” Multi Threads둜 μž‘λ™ν•΄ ν•œ λ²ˆμ— μ½”λ“œμ˜ μ—¬λŸ¬ 뢀뢄을 λ™μ‹œμ— μ‹€ν–‰ν•œλ‹€.

μ΄λŸ¬ν•œ Concurrent code λŠ” μ—¬λŸ¬ μž‘μ—…μ„ λ™μ‹œμ— μˆ˜ν–‰ν•  수 μžˆλ„λ‘ ν•œλ‹€. 이런 μ½”λ“œλ₯Ό μž‘μ„±ν•  λ•ŒλŠ” μ™ΈλΆ€ μ‹œμŠ€ν…œμ„ κΈ°λ‹€λ¦¬λŠ” μž‘μ—…μ„ μΌμ‹œ μ€‘λ‹¨ν•¨μœΌλ‘œμ¨ Memory-safeν•œ λ°©μ‹μœΌλ‘œ 더 μ‰½κ²Œ μž‘μ„±ν•  수 μžˆλ‹€.

Asynchronous code 와 Parallel code 둜 μΈν•œ scheduling μœ μ—°μ„± μΆ”κ°€λŠ” μ½”λ“œμ˜ λ³΅μž‘μ„± 증가λ₯Ό μˆ˜λ°˜ν•œλ‹€. λŒ€μ‹  Swift's language-level supportλ₯Ό μ§€μ›ν•˜μ—¬ Compiler κ°€ 문제λ₯Ό 찾을 수 μžˆλ„λ‘ ν•œλ‹€. 예λ₯Ό λ“€μ–΄ Actorλ₯Ό μ‚¬μš©ν•΄ mutable state에 μ•ˆμ „ν•˜κ²Œ μ ‘κ·Όν•  수 μžˆλ„λ‘ ν•˜λŠ” 것과 같은 μ˜λ„λ₯Ό ν‘œν˜„ν•˜λ„λ‘ ν•΄ compile-time checking을 κ°€λŠ₯μΌ€ ν•œλ‹€.

Concurrent code μ½”λ“œλ₯Ό μ‚¬μš©ν•  λ•Œ μœ μ˜ν•΄μ•Ό ν•  점은 이것이 λŠλ¦¬κ±°λ‚˜ 버그가 μžˆλŠ” μ½”λ“œλ₯Ό λΉ λ₯΄κ³  μ •ν™•ν•˜κ²Œ μž‘λ™ν•˜λ„λ‘ ν•΄μ€€λ‹€λŠ” 보μž₯이 μ—†λ‹€λŠ” 것이닀. 였히렀 Concurrency λŠ” μ½”λ“œμ˜ 디버깅을 μ–΄λ ΅κ²Œ ν•΄ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μ–΄λ ΅κ²Œ λ§Œλ“ λ‹€. Asynchronous code 와 Parallel code λŠ” 이 둜직이 ν•„μš”ν•œ κ³³μ—μ„œλ§Œ 적절히 μ‚¬μš©ν•΄μ•Όν•œλ‹€. Fetching data 와 같이 μ™ΈλΆ€ μš”μΈμ— μ˜ν•œ 지연을 κΈ°λ‹€λ¦¬λŠ” 것이 μ•„λ‹Œ λ‚΄λΆ€μ μœΌλ‘œ 느린 μ½”λ“œλŠ” μ½”λ“œμ˜ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직의 문제λ₯Ό μ°Ύμ•„ 해결해야지 Concurrency λ₯Ό 톡해 ν•΄κ²°ν•˜λ € ν•΄μ„œλŠ” μ•ˆ λœλ‹€.

Swift μ—μ„œ Concurrency model은 μŠ€λ ˆλ“œμ˜ μ΅œμƒλ‹¨μ—μ„œ μž‘λ™ν•˜μ§€λ§Œ μ§μ ‘μ μœΌλ‘œ μƒν˜Έμž‘μš© ν•˜μ§€ μ•ŠλŠ”λ‹€. Swift 의 Asynchronous Function 은 μ‹€ν–‰ 쀑인 μŠ€λ ˆλ“œλ₯Ό 쀑단할 수 μžˆλ‹€. 그러면 첫 번째 Asynchronous Function 이 μ€‘λ‹¨λœ λ™μ•ˆ 동일 ν”„λ‘œκ·Έλž¨μ˜ λ‹€λ₯Έ Asynchronous Function 이 ν•΄λ‹Ή μŠ€λ ˆλ“œμ—μ„œ 싀행될 수 μžˆλ‹€. λ”°λΌμ„œ Asynchronous Function 이 재개될 λ•Œ μ–΄λ–€ μŠ€λ ˆλ“œκ°€ κ·Έ ν•¨μˆ˜λ₯Ό 싀행할지에 λŒ€ν•΄ μ•„λ¬΄λŸ° 보μž₯도 ν•˜μ§€ μ•ŠλŠ”λ‹€.


Swift’s language support 없이도 Concurrent code λ₯Ό μž‘μ„±ν•  수 μžˆμœΌλ‚˜ μ½”λ“œλ₯Ό 읽기 μ–΄λ ΅λ‹€. μ•„λž˜ μ½”λ“œλŠ” Swift’s language support 없이 μž‘μ„±λœ Concurrent code 둜 κ°€λŸ¬λ¦¬μ—μ„œ 사진 이름 λͺ©λ‘μ„ λ‹€μš΄λ‘œλ“œν•˜κ³ , 이 λͺ©λ‘μ—μ„œ λ‹€μ‹œ 첫 번째 사진을 λ‹€μš΄λ‘œλ“œν•΄ μ‚¬μš©μžμ—κ²Œ λ³΄μ—¬μ£ΌλŠ” μ½”λ“œλ‹€.

listPhotos(inGallery: "Summer Vacation") { photoNames in
    let sortedNames = photoNames.sorted()
    let name = sortedNames[0]
    downloadPhoto(named: name) { photo in
        show(photo)
    }
}

κ°„λ‹¨ν•œ μ½”λ“œμ΄μ§€λ§Œ completion handlerκ°€ μ—°μ†μ μœΌλ‘œ μž‘μ„±λ˜μ–΄μ•Όν•˜λ―€λ‘œ Nested Closuresλ₯Ό μ‚¬μš©ν•΄μ•Όν•œλ‹€. λ¬Έμ œλŠ” 이런 μ½”λ“œκ°€ 더 λ³΅μž‘ν•΄μ§ˆ 경우 쀑첩은 더 λ§Žμ€ depth λ₯Ό κ°–κ²Œ 될 것이고, μ΄λŠ” μ½”λ“œλ₯Ό 닀루기 μ–΄λ ΅κ²Œ λ§Œλ“€ 것이닀. μ΄λŠ” TypeScript μ—μ„œ μ€‘μ²©λœ Promise 의 λ³΅μž‘ν•œ μ½”λ“œμ™€ async/await λ₯Ό μ‚¬μš©ν•œ 가독성 쒋은 μš°μ•„ν•œ μ½”λ“œλ₯Ό 비ꡐ해본 적 μžˆλ‹€λ©΄ μ–΄λ–€ μ˜λ―ΈμΈμ§€ μ‰½κ²Œ 이해가 갈 것이닀.

Swift’s language support λ₯Ό μ΄μš©ν•œ Asynchronous Functionsλ₯Ό μ‚¬μš©ν•œλ‹€λŠ” 것은 async/awaitλ₯Ό μ‚¬μš©ν•΄ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” 것을 μ˜λ―Έν•œλ‹€.

Asynchronous Functions λŠ” μ‹€ν–‰ 쀑인 μŠ€λ ˆλ“œλ₯Ό 쀑단할 수 μžˆλ‹€κ³  ν–ˆλ‹€. Asynchronous Functions μ•ˆμ—μ„œ μ‹€ν–‰μ˜ 흐름이 μ€‘λ‹¨λ˜λŠ” κ²½μš°λŠ” λ‹€λ₯Έ 비동기 ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λŠ” 경우만 ν•΄λ‹Ήλœλ‹€. 즉, await ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•΄ κΈ°λ‹€λ¦°λ‹€λŠ” 것은 λ‹€λ₯Έ 비동기 ν•¨μˆ˜μ˜ λ°˜ν™˜μ„ κΈ°λ‹€λ¦°λ‹€λŠ” μ˜λ―Έμ΄λ‹€(비동기 ν•¨μˆ˜ μ•ˆμ—μ„œ λ‹€λ₯Έ 비동기 ν•¨μˆ˜κ°€ μ•„λ‹Œ 일반 ν•¨μˆ˜λ‚˜ μ½”λ“œ μ•žμ— await 을 μ‚¬μš©ν•˜λŠ” 것은 아무 μ˜λ―Έκ°€ μ—†λ‹€).


2. Asynchronous Functions πŸ‘©β€πŸ’»

1. Asynchronous Syntax

Swift μ—μ„œ Asynchronous Functions λ₯Ό μ •μ˜ν•˜λŠ” 방법은 ν•¨μˆ˜λ₯Ό μ •μ˜ν•  λ•Œ arrow(->) μ•žμ— async keyword λ₯Ό μž‘μ„±ν•˜λŠ” κ²ƒμœΌλ‘œ μ •μ˜λœλ‹€.

func listPhotos(inGallery name: String) async -> [String] {
    let result = // ... some asynchronous networking code ...
    return result
}

Asynchronous Functions κ°€ μ—λŸ¬λ₯Ό throws ν•˜λŠ” 경우 async throws μˆœμ„œλ‘œ keyword λ₯Ό μž‘μ„±ν•œλ‹€.

func listPhotos(inGallery name: String) async throws -> [String] {
    let result = // ... some asynchronous networking code ...
    return result
}

2. async/await in TypeScript

μš°μ„  μ’€ 더 접근성이 μ‰½μš΄, async/awaitλ₯Ό κ°€μž₯ 많이 μ‚¬μš©ν•΄λ΄€μ„λ§Œν•œ μ½”λ“œλŠ” TypeScript 의 Promiseκ°€ μ•„λ‹κΉŒ μƒκ°λœλ‹€. TypeScript μ—μ„œ await λ₯Ό μ‚¬μš©ν•œλ‹€λŠ” 것은 Promise 객체λ₯Ό λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜μ˜ μ’…λ£Œλ₯Ό κΈ°λ‹€λ¦°λ‹€λŠ” 것을 μ˜λ―Έν–ˆλ‹€. μš°μ„  Asynchronous Functions λ₯Ό μ΄ν•΄ν•˜κΈ° μœ„ν•΄ TypeScript 예제λ₯Ό μ‚΄νŽ΄λ³΄μž.

기쑴의 Promise 객체λ₯Ό 직접 μƒμ„±ν•˜λ˜ 방식과 달리 async/await λ₯Ό μ‚¬μš©ν•˜λ©΄ then..then..then..catch...finally ν˜•νƒœμ˜ chaining λŒ€μ‹  μ„±κ³΅ν–ˆμ„ 경우 이미 resolved μƒνƒœμ˜ 값을 unwrapping ν•΄ λ°˜ν™˜ν•˜κ³ , μ—λŸ¬λŠ” catch λ₯Ό 톡해 μ²˜λ¦¬ν•œλ‹€.
즉, 일반 μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ“― μ½”λ”©ν•˜λ©° try-catchλ₯Ό 톡해 μ½”λ“œλ₯Ό μž‘μ„±ν•  수 있으며 μ½”λ“œμ˜ 쀑첩이 쀄어듀고 μ‹€ν–‰μ˜ 흐름을 νŒŒμ•…ν•˜κΈ° μ‰½λ‹€λŠ” μž₯점을 κ°€μ‘Œμ—ˆλ‹€.

const asyncStr: () => Promise<string> =
    async () => {
        // throw Error('throw error!!')
        return 'first'
    }
  • without async/await
const printOneTwo: () => void =
    () => {
        let str: Promise<string> = asyncStr()    // Must be returned as (Promise, state is resolved) or (Promise, state is reject)
        str.then((value: string) => console.log(value))
            .catch((error: string) => console.error(error))
            .finally(() => console.log('second'))
    }
printOneTwo()
first
second
  • with async/await
const printOneTwo: () => void =
    async () => {
        try {
            let str: string = await asyncStr()  // This returned as unwrapped, (string) or (Error)
            console.log(str)
        } catch (e) {
            console.error(e)
        }
        console.log('second')
    }
printOneTwo()
first
second

3. async/await in Swift

Swift 의 async/await 도 이와 μœ μ‚¬ν•˜λ‹€. awaitλ₯Ό μ‚¬μš©ν•œλ‹€λŠ” 것은 TaskλΌλŠ” ν•˜λ‚˜μ˜ μž‘μ—… λ‹¨μœ„κ°€ μ’…λ£Œλ˜κ³  return의 λ°˜ν™˜κ°’μ„ κΈ°λ‹€λ¦°λ‹€λŠ” 것을 μ˜λ―Έν•œλ‹€.

참고둜 async keyword 와 throws keyword λ₯Ό 함꼐 μ“Έ λ•ŒλŠ” async throws μˆœμ„œλ‘œ μž‘μ„±ν–ˆμœΌλ‚˜, await keyword 와 try keyword λ₯Ό ν•¨κ»˜ μ“Έ λ•ŒλŠ” try await μˆœμ„œλ‘œ μž‘μ„±ν•œλ‹€.

λ”°λΌμ„œ μœ„μ—μ„œ κ°€λŸ¬λ¦¬μ—μ„œ 사진 이름 λͺ©λ‘μ„ λ‹€μš΄λ‘œλ“œν•˜κ³ , 첫 번째 사진을 λ‹€μš΄λ‘œλ“œ ν›„ λ³΄μ—¬μ£ΌλŠ” μ½”λ“œ λŠ” λ‹€μŒκ³Ό 같이 변경될 수 μžˆλ‹€.

let photoNames = await listPhotos(inGallery: "Summer Vacation")
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
let photo = await downloadPhoto(named: name)
show(photo)
  1. await 쀑단점이 μžˆλŠ” listPhotos(inGallery:) ν•¨μˆ˜λ₯Ό 호좜 ν›„ return 이 λ°˜ν™˜λ  λ•ŒκΉŒμ§€ 싀행을 μ€‘λ‹¨ν•œλ‹€.
  2. 이 μ½”λ“œκ°€ μ€‘λ‹¨λœ λ™μ•ˆ long-running background task κ°€ ν•„μš”ν•œ 동일 ν”„λ‘œκ·Έλž¨μ˜ λ‹€λ₯Έ Concurrent code κ°€ μ‹€ν–‰λœλ‹€. λ‹€λ₯Έ Concurrent code μ—­μ‹œ λ‹€μŒ await 쀑단점이 ν‘œμ‹œλœ μ½”λ“œκΉŒμ§€ 진행 ν›„ μ€‘λ‹¨λ˜κ±°λ‚˜ 더 이상 쀑단점이 μ—†λ‹€λ©΄ ν•΄λ‹Ή ν•¨μˆ˜κ°€ μ’…λ£Œλ  λ•ŒκΉŒμ§€ 계속 μ§„ν–‰λœλ‹€.
  3. listPhotos(inGallery:)κ°€ return 되며 μ½”λ“œκ°€ μž¬μ‹œμž‘λ˜κ³ , λ³€μˆ˜ photoNames 에 λ°˜ν™˜λœ 값을 assign ν•œλ‹€.
  4. λ‹€μŒ 쀑단점인 awaitλ₯Ό λ§Œλ‚˜κΈ° μ „κΉŒμ§€ *Synchronous code λ₯Ό 진행*ν•œλ‹€.
  5. await 쀑단점이 μžˆλŠ” downloadPhoto(named:) ν•¨μˆ˜λ₯Ό 호좜 ν›„ λ§ˆμ°¬κ°€μ§€λ‘œ return 이 λ°˜ν™˜λ  λ•ŒκΉŒμ§€ 싀행을 μ€‘λ‹¨ν•œλ‹€. β€˜2.’ 와 λ§ˆμ°¬κ°€μ§€λ‘œ 이 μ½”λ“œκ°€ μ€‘λ‹¨λœ λ™μ•ˆ λ‹€λ₯Έ Concurrent code κ°€ μ‹€ν–‰λœλ‹€.
  6. downloadPhoto(named:)κ°€ return 되며 μ½”λ“œκ°€ μž¬μ‹œμž‘λ˜κ³ , λ³€μˆ˜ photo 에 λ°˜ν™˜λœ 값을 assign ν•œλ‹€.
  7. 이후 λ‹€λ₯Έ 쀑단점이 μ—†μœΌλ―€λ‘œ μ½”λ“œλŠ” λ‹€μ‹œ Synchronous ν•˜κ²Œ μ§„ν–‰λ˜μ–΄ show(photo)λ₯Ό ν˜ΈμΆœν•΄ 사진을 보여쀀닀.


await 쀑단점은 μ½”λ“œμ˜ 싀행을 μ€‘λ‹¨ν•˜κ³  ν•΄λ‹Ή μŠ€λ ˆλ“œμ—μ„œ λ‹€λ₯Έ μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜κΈ° λ•Œλ¬Έμ— 이λ₯Ό μŠ€λ ˆλ“œ 양보(yielding the thread)라고 λΆ€λ₯Έλ‹€. 이것은 μ½”λ“œμ˜ 싀행을 쀑단할 수 μžˆμ–΄μ•Όν•˜λ―€λ‘œ, μ•±μ˜ νŠΉμ • μœ„μΉ˜μ—μ„œλ§Œ Asynchronous Functions/Methodsλ₯Ό ν˜ΈμΆœν•  수 있으며 κ·Έ νŠΉμ • μœ„μΉ˜λŠ” λ‹€μŒκ³Ό κ°™λ‹€.

  • Asynchronous Function/Method/Property 의 context.
    (async keyword λ₯Ό μ‚¬μš©ν•œ Closure λ₯Ό μƒκ°ν•˜λ©΄ λœλ‹€)
  • @main이 marked 된 Structure/Class/Enumeration 의 static main() λ©”μ„œλ“œμ˜ context
  • Unstructured Concurrency 에 λ‚˜μ˜¨ 것과 같은 Unstructured child task

4. Encapsulation the Code within an Asynchronous Code

비동기 ν•¨μˆ˜ λ‚΄μ—μ„œ await keyword μ™Έ λ‹€λ₯Έ μ½”λ“œλŠ” Synchronous 둜 μž‘λ™ν•˜λ©° μ½”λ“œλ₯Ό 순차적으둜 μ‹€ν–‰ν•œλ‹€. ν•˜μ§€λ§Œ 이것 λ§ŒμœΌλ‘œλŠ” μΆ©λΆ„ν•˜μ§€ μ•Šμ€ μΌ€μ΄μŠ€κ°€ μ‘΄μž¬ν•œλ‹€. λ‹€μŒ μ½”λ“œλŠ” 사진을 Road Trip κ°€λŸ¬λ¦¬μ— μΆ”κ°€ν•˜κ³ , Summer Vacation κ°€λŸ¬λ¦¬μ—μ„œ μ‚­μ œν•˜λŠ” μ½”λ“œλ‹€.

let firstPhoto = await listPhotos(inGallery: "Summer Vacation")[0]
add(firstPhoto toGallery: "Road Trip")
// At this point, firstPhoto is temporarily in both galleries.
remove(firstPhoto fromGallery: "Summer Vacation")

그리고 add(_:toGallery:)와 remove(_:fromGallery:) 사이에 λ‹€λ₯Έ μ½”λ“œλŠ” μ—†λ‹€. μΌμ‹œμ μ΄μ§€λ§Œ 이 μˆœκ°„ 사진은 μ–‘μͺ½ λͺ¨λ‘μ— μ‘΄μž¬ν•˜κ²Œλ˜κ³ , μ•±μ˜ λΆˆλ³€μ„±(invariant) 쀑 ν•˜λ‚˜λ₯Ό μœ„λ°˜ν•œλ‹€. λ§Œμ•½, 이 두 μ½”λ“œ 사이에 await κ°€ μΆ”κ°€λœλ‹€λ©΄ μ•±μ˜ λΆˆλ³€μ„± μœ„λ°˜μ€ μΌμ‹œμ μ΄ μ•„λ‹ˆλΌ 였랜 μ‹œκ°„ 지속될 μˆ˜λ„ μžˆκ²Œλœλ‹€.
λ”°λΌμ„œ 이 μ½”λ“œ 덩어리(chunk)λŠ” await keyword κ°€ μΆ”κ°€λ˜λ©΄ μ•ˆ λœλ‹€λŠ” 것을 λͺ…μ‹œμ μœΌλ‘œ ν‘œν˜„ν•˜κ³  λΆ„λ¦¬μ‹œν‚€κΈ° μœ„ν•΄ 이λ₯Ό λ¦¬νŒ©ν† λ§ν•΄ Synchronous Function/Closure둜 λΆ„λ¦¬μ‹œμΌœμ•Όν•œλ‹€.

func move(_ photoName: String, from source: String, to destination: String) {
    add(photoName, to: destination)
    remove(photoName, from: source)
}
// ...
let firstPhoto = await listPhotos(inGallery: "Summer Vacation")[0]
move(firstPhoto, from: "Summer Vacation", to: "Road Trip")

이둜써 move(_:from:to:) ν•¨μˆ˜λŠ” await 쀑단점을 μΆ”κ°€ν•  경우 Swift’s language-level support μ•  μ˜ν•΄ compile-time error κ°€ λ°œμƒν•˜λ―€λ‘œ(async κ°€ λͺ…μ‹œλ˜μ–΄ μžˆμ§€ μ•ŠμœΌλ―€λ‘œ Synchronous Function 이닀), Synchronous μž‘λ™μ„ 보μž₯받을 수 μžˆλ‹€.


3. Asynchronous Sequences πŸ‘©β€πŸ’»

μ•žμ—μ„œ λ³Έ listPhotos(inGallery:) ν•¨μˆ˜λŠ” Asynchronous Function 으둜 Collection 이 λͺ¨λ‘ 쀀비될 λ•ŒκΉŒμ§€ κΈ°λ‹€λ Έλ‹€ κ²°κ³Όλ₯Ό ν•œ λ²ˆμ— Array 둜 returnν•œλ‹€.

그리고 이와 λ‹€λ₯Έ μ ‘κ·Ό λ°©λ²•μœΌλ‘œ, Asynchronous Sequenceκ°€ μžˆλ‹€. 이것은 Collection 이 λͺ¨λ‘ 쀀비될 λ•ŒκΉŒμ§€ 기닀리지 μ•Šκ³ , μ€€λΉ„ λ˜λŠ” elementsλ₯Ό μ§€μ†μ μœΌλ‘œ return ν•˜λŠ” 것이닀. 즉, Collection 이 λͺ¨λ‘ 쀀비될 λ•ŒκΉŒμ§€ 기닀리지 μ•Šκ³  Iterating을 ν•  수 μžˆλ‹€.

Iterating Over an Asynchronous SequenceλŠ” for-await-in을 μ΄μš©ν•΄ μ ‘κ·Όν•œλ‹€.

import Foundation

let handle = FileHandle.standardInput
for try await line in handle.bytes.lines {
    print(line)
}

μœ„ μ½”λ“œμ—μ„œ handle 은 파일의 λͺ¨λ“  데이터λ₯Ό ν•œ λ²ˆμ— μ€€λΉ„ν•˜μ§€ μ•Šκ³  라인 ν•˜λ‚˜λ₯Ό 읽은 ν›„ iteration이 진행됨에 따라 쀑단/재개λ₯Ό λ°˜λ³΅ν•œλ‹€.

Custom Types λ₯Ό λ§Œλ“€ λ•Œ iteration을 ν•˜λ„λ‘ ν•˜κΈ° μœ„ν•΄μ„œλŠ” λ‹€μŒ protocol 의 채택이 ν•„μš”ν•˜λ‹€.

  • Sequence protocol 을 μ±„νƒν•˜λ©΄ for-in loop μ‚¬μš©μ΄ κ°€λŠ₯ν•˜λ‹€.
  • AsyncSequence protocol 을 μ±„νƒν•˜λ©΄ for-await-in loop μ‚¬μš©μ΄ κ°€λŠ₯ν•˜λ‹€.

Swift 의 for-await-in은 JavaScript 의 for-await-of와 λΉ„κ΅ν•΄μ„œ 보면 쒋을 것 κ°™λ‹€.


4. Calling Asynchronous Functions in Parallel πŸ‘©β€πŸ’»

downloadPhoto(named:) ν•¨μˆ˜λŠ” Fetching data λ₯Ό ν•˜λŠ” ν•¨μˆ˜λ‘œ Asynchronous 둜 μž‘λ™ν•œλ‹€.

λ”°λΌμ„œ await 쀑단점을 λ§Œλ‚˜ μ½”λ“œκ°€ μ€‘λ‹¨λœ λ™μ•ˆ λ‹€λ₯Έ Concurrent code κ°€ 싀행될 수 μžˆμ§€λ§Œ λ‹€μŒκ³Ό 같은 κ²½μš°λŠ” λ‹€λ₯Έ Concurrent code κ°€ μ•„λ‹Œ λ™μΌν•œ Asynchronous Functions/Methods 에 μ†ν•΄μžˆμœΌλ―€λ‘œ 맀번 awaitλ₯Ό λ§Œλ‚  λ•Œλ§ˆλ‹€ β€˜μ½”λ“œ 싀행을 μ€‘λ‹¨ν•˜κ³  λ‹€μš΄λ‘œλ“œν•˜κ³  μž¬κ°œν•˜λŠ” 과정을 λ°˜λ³΅β€™ ν•œλ‹€.
즉, μ•žμ— μ‘΄μž¬ν•˜λŠ” 쀑단점이 μš”μ²­ν•œ 사진이 μ™„μ „νžˆ λ‹€μš΄λ‘œλ“œ 되기λ₯Ό κΈ°λ‹€λ¦° ν›„ 순차적으둜 λ‹€μš΄λ‘œλ“œ λ°›λŠ”λ‹€.

let firstPhoto = await downloadPhoto(named: photoNames[0])
let secondPhoto = await downloadPhoto(named: photoNames[1])
let thirdPhoto = await downloadPhoto(named: photoNames[2])

let photos = [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

사진은 λ©€ν‹° λ‹€μš΄λ‘œλ“œλ₯Ό ν•˜λŠ” 것이 더 νš¨μœ¨μ μ΄λ‹€. λ”°λΌμ„œ μœ„ 3개의 Asynchronous Function 은 λ‹€μŒκ³Ό 같이 ν•˜λ‚˜μ˜ μ€‘λ‹¨μ μœΌλ‘œ λ¬Άμ–΄ 관리할 수 μžˆλ‹€.

async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])

let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)

Asynchronous Function 이 호좜된 ν›„ return 이 λ°˜ν™˜λ˜λŠ” μ‹œμ μ— awaitλ₯Ό κ±°λŠ” 것이 μ•„λ‹ˆλΌ,
λ³€μˆ˜μ— 데이터가 assign λ˜λŠ” 것을 기닀리도둝 Asynchronous Propertyλ₯Ό μ΄μš©ν•˜κ³ , 이λ₯Ό Array에 λ‹΄μ•„ awaitλ₯Ό κ±Έμ–΄μ€€λ‹€.
μ΄λ ‡κ²Œ ν•˜λ©΄ 각각의 downloadPhoto(named:) ν•¨μˆ˜λŠ” await 쀑단점이 μ—†κΈ° λ•Œλ¬Έμ— λ‹€μš΄λ‘œλ“œλ₯Ό 기닀리지 μ•Šκ³  λ‹€μŒ downloadPhoto(named:)λ₯Ό ν˜ΈμΆœν•΄ λ™μ‹œμ— μ—¬λŸ¬ 개의 Asynchronous Function λ₯Ό ν˜ΈμΆœν•˜κ³ , 이 Asynchronous Property κ°€ λ‹΄κΈ΄ Array에 await 쀑단점이 κ±Έλ € 있기 λ•Œλ¬Έμ— Array 에 λͺ¨λ“  값이 assign λ˜λŠ” 것을 κΈ°λ‹€λ¦° ν›„ μž¬κ°œλœλ‹€.

Swift 의 await [func1, func2]은 JavaScript 의 Promise.all()와 λΉ„κ΅ν•΄μ„œ 보면 쒋을 것 κ°™λ‹€.

const [result1, result2] = await Promise.all([func1(), func2()])

5. Tasks and Task Groups πŸ‘©β€πŸ’»

1. Structured Concurrency

TaskλŠ” ν”„λ‘œκ·Έλž¨μ˜ 일뢀λ₯Ό Asynchronously ν•˜κ²Œ μ‹€ν–‰ν•  수 μžˆλŠ” μž‘μ—…μ˜ λ‹¨μœ„(A unit of asynchronous work)λ₯Ό λ§ν•˜λ©°, λͺ¨λ“  Asynchronous code λŠ” Task 의 μΌλΆ€λ‘œμ¨ μ‹€ν–‰λœλ‹€. μ•žμ—μ„œ λ³Έ async let syntax λŠ” Task 내에 Child Taskλ₯Ό λ§Œλ“€μ–΄ λ‚Έλ‹€. Child Task κ°€ μ—¬λŸ¬ 개일 경우 이λ₯Ό κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ Task Group을 μƒμ„±ν•˜κ³ , 이 그룹에 Child Task λ₯Ό μΆ”κ°€ν•  수 μžˆλ‹€. 이λ₯Ό κ·Έλ£Ήν™” ν•¨μœΌλ‘œμ¨ μš°μ„ μˆœμœ„μ™€ μ·¨μ†Œλ₯Ό 더 잘 μ œμ–΄ν•  수 있으며, λ™μ μœΌλ‘œ μž‘μ—…μ˜ 수λ₯Ό 생성할 수 μžˆλ‹€.

await withTaskGroup(of: Data.self) { taskGroup in
    let photoNames = await listPhotos(inGallery: "Summer Vacation")
    for name in photoNames {
        taskGroup.addTask { await downloadPhoto(named: name) }
    }
}

Task Group κ³Ό 각 Task λŠ” parent-child ꡬ쑰λ₯Ό κ°–λŠ”λ‹€. λ”°λΌμ„œ Task Group λ‚΄ 각각의 Child Task λŠ” λ™μΌν•œ Parent Taskλ₯Ό κ°–λŠ”λ‹€. 그리고 이 각각의 Child Task λŠ” 또 λ‹€λ₯Έ Child Task λ₯Ό κ°€μ§ˆ 수 μžˆλ‹€. 이듀은 Task Group 으둜 묢인 hierarchy ꡬ쑰λ₯Ό μ±„νƒν•˜κ³  있으며, 이듀 Task Group κ³Ό Tasks 관계λ₯Ό Structured Concurrency라 ν•œλ‹€.

Structured Concurrency λŠ” 정확성에 λŒ€ν•œ 일뢀 μ±…μž„(some responsibility for correctness)이 μ‚¬μš©μžμ—κ²Œ μ£Όμ–΄μ§€μ§€λ§Œ 이둜써 Swift λŠ” Propagating Cancellation을 μ²˜λ¦¬ν•  수 있으며, compile-time errorλ₯Ό 감지할 수 μžˆλ‹€.

  • Task에 λŒ€ν•œ μΆ”κ°€ μ •λ³΄λŠ” Task λ₯Ό μ°Έκ³ ν•œλ‹€.
  • Task Group에 λŒ€ν•œ μΆ”κ°€ μ •λ³΄λŠ” TaskGroup 을 μ°Έκ³ ν•œλ‹€.

2. Unstructured Concurrency

Structured Concurrency μ—μ„œ Tasks λŠ” Task Group 에 속해 λ™μΌν•œ Parent Taskλ₯Ό κ°–λŠ” 것과 달리 Unstructured TaskλŠ” Parent Taskλ₯Ό 갖지 μ•ŠλŠ”λ‹€. 이λ₯Ό Unstructured Concurrency라 ν•œλ‹€.

λ”°λΌμ„œ ν”„λ‘œκ·Έλž¨μ΄ μš”κ΅¬ν•˜λŠ”λŒ€λ‘œ Unstructured Taskλ₯Ό 관리할 수 μžˆλŠ” μ™„μ „ν•œ μœ μ—°μ„±(complete flexibility)을 κ°–λŠ” λŒ€μ‹ , 정확성에 λŒ€ν•œ μ™„μ „ν•œ μ±…μž„(completely responsibility for correctness)이 μ‚¬μš©μžμ—κ²Œ 주어진닀.

With great flexibility comes great responsibility


  1. ν˜„μž¬ Actor μ—μ„œ μ‹€ν–‰λ˜λŠ” Unstructured Taskλ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄μ„œλŠ” Task.init(priority:operation:) initializer λ₯Ό ν˜ΈμΆœν•΄μ•Όν•œλ‹€.
  2. ν˜„μž¬ Actor κ°€ μ•„λ‹Œ λΆ„λ¦¬λœ μž‘μ—…(detached task)으둜 Unstructured Taskλ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄μ„œλŠ” Task.detached(priority:operation:) class method λ₯Ό ν˜ΈμΆœν•΄μ•Όν•œλ‹€.

두 μž‘μ—…μ€ λͺ¨λ‘ κ²°κ³Όλ₯Ό κΈ°λ‹€λ¦¬κ±°λ‚˜(wait), μ·¨μ†Œν•˜λŠ”(cancel) μƒν˜Έ μž‘μš©μ„ ν•  수 μžˆλŠ” Taskλ₯Ό λ°˜ν™˜ν•œλ‹€.

let newPhoto = // ... some photo data ...
let handle = Task {
    return await add(newPhoto, toGalleryNamed: "Spring Adventures")
}
let result = await handle.value

3. Task Cancellation

Swift 의 Concurrency λŠ” ν˜‘λ™ μ·¨μ†Œ λͺ¨λΈ(Cooperative Cancellation Model)을 μ‚¬μš©ν•œλ‹€. 각의 Tasks λŠ” μ‹€ν–‰ 쀑 μ μ ˆν•œ μ‹œμ μ— μ·¨μ†Œλ˜μ—ˆλŠ”μ§€λ₯Ό 확인 ν›„, μ μ ˆν•œ λ°©μ‹μœΌλ‘œ μ·¨μ†Œμ— μ‘λ‹΅ν•œλ‹€.

Task Cancellation은 μˆ˜ν–‰μ€‘μΈ μž‘μ—…μ— λ”°λ₯΄λ©°, 일반적으둜 λ‹€μŒ 쀑 ν•˜λ‚˜λ₯Ό μ˜λ―Έν•œλ‹€.

  • Throwing an error like CancellationError
  • Returning nil or an empty collection
  • Returning the partially completed work

μž‘μ—…μ΄ μ·¨μ†Œλ˜μ—ˆλŠ”μ§€λ₯Ό ν™•μΈν•˜λ €λ©΄ λ‹€μŒ λ‘˜ 쀑 ν•œ 가지 방법을 μ‚¬μš©ν•œλ‹€.

  • Task κ°€ μ·¨μ†Œλ˜λ©΄ CancellationErrorλ₯Ό throw ν•˜λŠ” Type Method Task.checkCancellation λ₯Ό ν˜ΈμΆœν•œλ‹€.
  • Type Property Task.isCancelled 의 값을 ν™•μΈν•œλ‹€.

그리고 μ·¨μ†Œκ°€ ν™•μΈλœλ‹€λ©΄, ν˜„μž¬μ˜ μ½”λ“œμ—μ„œ μ·¨μ†Œλ₯Ό 처리(handle)ν•΄μ•Όν•œλ‹€. 예λ₯Ό λ“€μ–΄, downloadPhoto(named:)이 μ·¨μ†Œλœ 경우, 1. λΆ€λΆ„ λ‹€μš΄λ‘œλ“œλ₯Ό μ‚­μ œν•˜κ³ , 2. λ„€νŠΈμ›Œν¬ 접속을 λ‹«μŒμ„ μ²˜λ¦¬ν•΄μ•Όν•œλ‹€. 그리고 μ·¨μ†Œλ₯Ό μˆ˜λ™μœΌλ‘œ μ „νŒŒν•˜λ €λ©΄ Instance Method Task.cancel() 을 ν˜ΈμΆœν•œλ‹€.


6. Actors πŸ‘©β€πŸ’»

1. Actors in Swift

ν”„λ‘œκ·Έλž¨μ„ isolated, concurrent pieces둜 λΆ„λ¦¬μ‹œν‚€κΈ° μœ„ν•΄ Tasks λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€. 기본적으둜 Tasks λŠ” isolated λ˜μ–΄ μžˆμ–΄ λ™μ‹œμ— μ‹€ν–‰ν•˜λŠ” 것이 μ•ˆμ „ν•˜μ§€λ§Œ Tasks 사이에 정보λ₯Ό κ³΅μœ ν•  ν•„μš”κ°€ μžˆλŠ”λ° μ΄λ•Œ Actorsλ₯Ό μ‚¬μš©ν•œλ‹€. Actors λŠ” Concurrent code 간에 정보λ₯Ό μ•ˆμ „ν•˜κ²Œ κ³΅μœ ν•  수 있게 ν•œλ‹€.

Actors λŠ” Reference Types둜 Classes 와 λΉ„μŠ·ν•˜μ§€λ§Œ, Classes 와 λ‹€λ₯΄κ²Œ Actor λŠ” λ™μ‹œμ— ν•˜λ‚˜μ˜ Task 만 mutable state의 접근을 ν—ˆμš©ν•˜λ―€λ‘œ, μ—¬λŸ¬ Tasks κ°€ λ™μ‹œμ— ν•˜λ‚˜μ˜ Actor instance 와 μƒν˜Έμž‘μš©ν•΄λ„ μ•ˆμ „ν•˜λ‹€.

즉, Actors 의 mutable state 에 μ ‘κ·Όν•˜κΈ° μœ„ν•΄μ„œλŠ” isolated 된 Task λ‹¨μœ„λ‘œ μ ‘κ·Όν•΄μ•Όν•œλ‹€. 이둜 인해 μ ‘κ·Όν•˜λŠ” μ¦‰μ‹œ μš”μ²­ν•œ 값을 λ°˜ν™˜ λ°›λŠ”λ‹€λŠ” 보μž₯이 μ—†κΈ° λ•Œλ¬Έμ— Actor 의 Variable Properties λ˜λŠ” Methods 에 μ ‘κ·Όν•˜κΈ° μœ„ν•΄μ„œλŠ” λ°˜λ“œμ‹œ await을 μ‚¬μš©ν•΄ μ ‘κ·Όν•΄μ•Όν•œλ‹€.

  • let으둜 μ„ μ–Έν•œ μƒμˆ˜μ— μ ‘κ·Όν•  λ•ŒλŠ” await keyword λ₯Ό λͺ…μ‹œν•˜μ§€ μ•Šμ•„λ„ λœλ‹€. immutable이기 λ•Œλ¬Έμ΄λ‹€.
  • var둜 μ„ μ–Έν•œ λ³€μˆ˜λΌ ν•˜λ”λΌλ„ 이 λ³€μˆ˜λŠ” actor-isolated propertiesμ΄λ―€λ‘œ μ™ΈλΆ€ contextμ—μ„œ μž„μ˜λ‘œ 값을 μˆ˜μ •ν•˜λŠ” 것은 λΆˆκ°€λŠ₯ν•˜λ‹€. mutable이기 λ•Œλ¬Έμ— λ°˜λ“œμ‹œ await keyword λ₯Ό μ΄μš©ν•΄ μ ‘κ·Όν•΄μ•Όν•œλ‹€.
  • λ©”μ„œλ“œλŠ” λ°˜ν™˜κ°’μ΄ μ—†λŠ” λ©”μ„œλ“œλΌ ν•˜λ”λΌλ„ μ•”μ‹œμ μœΌλ‘œ VoidλΌλŠ” νƒ€μž… νŠΉμˆ˜ν•œ κ°’(() 둜 쓰여진 Empty Tuple)을 λ°˜ν™˜ν•œλ‹€.
    그리고 λ‹¨μˆœνžˆ λ©”μ„œλ“œμ˜ νƒ€μž…λ§ŒμœΌλ‘œλŠ” 이 λ©”μ„œλ“œκ°€ Actor의 mutable state와 μƒν˜Έμž‘μš©μ„ ν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” 것을 보μž₯ν•  수 μ—†λ‹€. 예λ₯Ό λ“€μ–΄ λ”°λΌμ„œ Dictionaries의 값을 μ‘°νšŒμ‹œ 항상 Optional둜 λ°˜ν™˜ν•˜λŠ” κ²ƒμ²˜λŸΌ Actor의 λͺ¨λ“  λ©”μ„œλ“œλŠ” ν˜ΈμΆœμ‹œ 항상 await keyword λ₯Ό μ΄μš©ν•΄ μ ‘κ·Όν•΄μ•Όν•œλ‹€.

λ‹€μŒ μ˜ˆμ œλŠ” μ˜¨λ„λ₯Ό κΈ°λ‘ν•˜λŠ” Actorλ‹€.

actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }
}

Actors λŠ” actor keyword λ₯Ό μ΄μš©ν•΄ μ •μ˜ν•œλ‹€. μœ„ TemperatureLogger Actor λŠ” 3개의 properties λ₯Ό 가지고 있으며, κ·Έ 쀑 max λŠ” var둜 μ„ μ–Έλ˜μ—ˆμœΌλ©°, private(set) modifier μ•  μ˜ν•΄ get 은 internal, set 은 private의 Access Level 을 κ°–λŠ”λ‹€.

2. Actor Isolation

Swift λŠ” Actor 의 local state에 μ ‘κ·Όν•  수 μžˆλŠ” 것은 Actor 의 context둜 μ œν•œν•¨μœΌλ‘œμ¨ Asynchronous workμ—μ„œλ„ mutable stateλ₯Ό μ•ˆμ „ν•˜κ²Œ κ³΅μœ ν•  수 μžˆμŒμ„ 보μž₯(guarantee)ν•œλ‹€.

μž μ‹œ 후에 μžμ„Ένžˆ μ‚΄νŽ΄λ³΄κ² μ§€λ§Œ, 이 보μž₯μ„±μœΌλ‘œ Actor 의 let properties λ₯Ό μ œμ™Έν•œ λͺ¨λ“  var properties 와 MethodsλŠ” λ°˜λ“œμ‹œ await keyword λ₯Ό μ΄μš©ν•΄ μ ‘κ·Όν•΄μ•Όν•˜λ©°, 그렇지 μ•ŠμœΌλ©΄ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.

let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
print(await logger.max)    // 25

Swift 의 이런 보μž₯성을 Actor Isolation이라 ν•œλ‹€.

3. Class with private properties

Actor κ°€ Class 와 μ–΄λ–»κ²Œ λ‹€λ₯Έμ§€ μ•Œμ•„λ³΄κΈ° μœ„ν•΄ μœ„μ™€ λ‹€μŒκ³Ό 같이 TemperatureLogger λ₯Ό Class λ₯Ό λ§Œλ“€μ–΄ Actor 와 λΉ„κ΅ν•΄λ³΄λ„λ‘ν•˜μž.

class TemperatureLogger {
    let label: String
    var measurements: [Int]
    private var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }

    func getMax() -> Int {
        max
    }
}
let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
print(logger.label)     // Outdoors
print(logger.max)       // error: 'max' is inaccessible due to 'private' protection level
print(logger.getMax())  // 25

private modifier 에 μ˜ν•΄ get κ³Ό set λͺ¨λ‘ private의 Access Level 을 κ°–κΈ° λ•Œλ¬Έμ— μ™ΈλΆ€ context μ—μ„œ 직접 접근이 λΆˆκ°€λŠ₯ν•˜λ‹€.

4. Class with private(set) properties

이제 max 의 modifier λ₯Ό private(set)으둜 λ°”κΏ”λ³΄μž.

class TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }

    func getMax() -> Int {
        max
    }
}
let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
print(logger.label)     // Outdoors
print(logger.max)       // 25
print(logger.getMax())  // 25

이제 max property λŠ” private(set)μ΄λ―€λ‘œ get 은 internal, set 은 private의 Access Level 을 κ°–κΈ° λ•Œλ¬Έμ— getter λ©”μ„œλ“œ 없이 μ™ΈλΆ€μ—μ„œ 접근이 κ°€λŠ₯ν•˜λ‹€.

5. Actor with private property

κ·Έλ ‡λ‹€λ©΄ Actor μ—μ„œμ˜ private은 μ–΄λ–»κ²Œ μž‘λ™ν• κΉŒ?

actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }

    func getMax() -> Int {
        max
    }

    func greeting(name: String) {
        print("Hello~ \(name)")
    }
}
Task {
    let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
    print(logger.label)                             // Outdoors
    print(logger.max)                               // error: 'max' is inaccessible due to 'private' protection level
    print(await logger.getMax())                    // 25
    await logger.greeting(name: "Actor Methods")    // Hello~ Actor Methods
}
  • logger.label : let으둜 μ„ μ–Έλ˜μ–΄ μžˆλ‹€λ©΄ immutableμ΄λ―€λ‘œ Class 와 λ§ˆμ°¬κ°€μ§€λ‘œ μ™ΈλΆ€ contextμ—μ„œ 자유둭게 접근이 κ°€λŠ₯ν•˜λ‹€.
  • logger.max : getκ³Ό set λͺ¨λ‘ private의 Access Level 을 κ°–κΈ° λ•Œλ¬Έμ— μ™ΈλΆ€ context μ—μ„œ 직접 접근이 λΆˆκ°€λŠ₯ν•˜λ‹€.
  • logger.getMax() : getMax() λ©”μ„œλ“œλŠ” Actor 의 λ©”μ„œλ“œμ΄λ―€λ‘œ await을 μ΄μš©ν•΄ μ ‘κ·Όν•΄μ•Όν•œλ‹€.
  • logger.greeting(name:) : μ–΄λ– ν•œ mutable state와 μƒν˜Έμž‘μš©μ„ ν•˜μ§€ μ•ŠλŠ”λ‹€. ν•˜μ§€λ§Œ greeting(name:) λ©”μ„œλ“œ μ—­μ‹œ Actor 의 λ©”μ„œλ“œμ΄λ―€λ‘œ await을 μ΄μš©ν•΄ μ ‘κ·Όν•΄μ•Όν•œλ‹€.

6. Actor with private(set) property

이제 μ›λž˜λŒ€λ‘œ λŒμ•„μ™€ private(set)으둜 λ°”κΏ”λ³΄μž.

actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }

    func getMax() -> Int {
        max
    }
}
Task {
    let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
    print(await logger.label)               // Outdoors, No 'async' operations occur within 'await' expression
    print(logger.label)                     // Outdoors
    logger.measurements[0] = 0              // error: actor-isolated property 'measurements' can not be mutated from a non-isolated context
    print(logger.max)                       // error: expression is 'async' but is not marked with 'await'
    print("1. \(await logger.max)")         // 1. 25
    await print("2. \(logger.max)")         // 2. 25
    print("3. \(await logger.getMax())")    // 3. 25
    await print("4. \(logger.getMax())")    // 4. 25
}

μ΄λ²ˆμ—” λͺ¨λ“  μΌ€μ΄μŠ€μ— λŒ€ν•΄ μ‚΄νŽ΄λ³΄λ©° Actor 의 mutable state와 immutable의 차이도 ν•¨κ»˜ μ‚΄νŽ΄λ³Έλ‹€.

  • await logger.label : let으둜 μ„ μ–Έν•œ μƒμˆ˜μ΄λ―€λ‘œ λΉ„λ™κΈ°λ‘œ μž‘λ™ν•˜μ§€ μ•ŠλŠ”λ‹€. λ”°λΌμ„œ 정상 μž‘λ™ν•˜μ§€λ§Œ awaitλŠ” λ¬΄μ‹œλ˜κ³  μ»΄νŒŒμΌλŸ¬λŠ” await을 μ§€μšΈ 것을 μš”κ΅¬ν•œλ‹€.
  • logger.label : let으둜 μ„ μ–Έλ˜μ–΄ immutableμ΄λ―€λ‘œ await 없이도 Actor 의 값에 μ •μƒμ μœΌλ‘œ μ ‘κ·Όν•  수 μžˆλ‹€.
    (단, Actor μžμ²΄μ— λŒ€ν•œ 접근은 λ°˜λ“œμ‹œ Task μ•ˆμ—μ„œ μ΄λ£¨μ–΄μ Έμ•Όν•œλ‹€)
  • logger.measurements[0] = 0 : var둜 μ„ μ–Έλ˜μ—ˆμ§€λ§Œ measurements λŠ” actor-isolated property μ΄λ―€λ‘œ Actor 의 context μ™ΈλΆ€μ—μ„œ μˆ˜μ •μ΄ λΆˆκ°€λŠ₯ν•˜λ‹€.
  • logger.max : private(set)μ΄λ―€λ‘œ get 은 internal, set 은 private의 Access Level 을 κ°–κΈ° λ•Œλ¬Έμ— Class 와 λ§ˆμ°¬κ°€μ§€λ‘œ getter λ©”μ„œλ“œ 없이 μ™ΈλΆ€μ—μ„œ 접근이 κ°€λŠ₯ν•˜λ‹€. ν•˜μ§€λ§Œ var 이기 λ•Œλ¬Έμ— await 없이 μ ‘κ·Όν•˜λŠ” 것은 λΆˆκ°€λŠ₯ν•˜λ‹€.
  • logger.max / logger.getMax() : print(_:) argument 에 await을 κ±Έλ“ , print(_:) ν˜ΈμΆœμ— await을 κ±Έλ“  λͺ¨λ‘ μ •μƒμ μœΌλ‘œ μž‘λ™ν•œλ‹€.

7. Extensions of Actor

actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }
}

extension TemperatureLogger {
    func update(with measurement: Int) {
        measurements.append(measurement)
        if measurement > max {
            max = measurement
        }
    }
}

Swift 의 Extensions λŠ” extension keyword λ₯Ό μ΄μš©ν•΄ Class, Structure, Enumeration, Protocol 을 ν™•μž₯ν•œλ‹€. μ΄λŠ” Objective-C 의 Categories 와 μœ μ‚¬ν•˜λ‹€.

즉, update(with:) λ©”μ„œλ“œλŠ” 이미 Actor 내뢀에 μžˆλŠ” 것이기 λ•Œλ¬Έμ— Actor 의 context에 ν¬ν•¨λ˜λ―€λ‘œ await keyword 없이 mutable state에 μ ‘κ·Όν•  수 μžˆλ‹€.


7. Sendable Types πŸ‘©β€πŸ’»

1. Concurrency Domain

Tasks 와 Actors λŠ” ν”„λ‘œκ·Έλž¨μ˜ 일뢀λ₯Ό 쑰각으둜 λΆ„λ¦¬μ‹œμΌœ Concurrent code κ°€ μ•ˆμ „ν•˜λ„λ‘ λ§Œλ“ λ‹€. Task λ˜λŠ” Actor instance 의 내뢀에 var둜 μ„ μ–Έλœ mutable stateλ₯Ό ν¬ν•¨ν•˜λŠ” κ²½μš°κ°€ μžˆλŠ”λ° 이λ₯Ό Concurrency domain이라 ν•œλ‹€. μ΄λ ‡κ²Œ mutable state λ₯Ό ν¬ν•¨ν•˜μ§€λ§Œ λ™μ‹œ μ ‘κ·Ό(overlapping access)에 λŒ€ν•΄ λ³΄ν˜Έλ˜μ§€ μ•ŠλŠ” κ²½μš°λŠ” Concurrency domain 간에 곡유될 수 μ—†λ‹€.

2. Sendable Protocol

Concurrency domain 간에 곡유될 수 μžˆλŠ” νƒ€μž…μ„ Sendable Types라 ν•œλ‹€. Sendable Types λŠ” Actor 의 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œ arguments 둜 μ „λ‹¬λ˜κ±°λ‚˜ Task 의 결과둜써 λ°˜ν™˜λ  수 μžˆλ‹€.

Value Types λŠ” μ–Έμ œλ‚˜ μ•ˆμ „ν•œ κ³΅μœ κ°€ κ°€λŠ₯ν•˜λ‹€. λ”°λΌμ„œ Concurrency domain 간에도 μ•ˆμ „ν•˜κ²Œ κ³΅μœ ν•  수 μžˆλ‹€.

반면, Reference Types λŠ” Concurrency domain 간에 μ „λ‹¬ν•˜κΈ°μ— μ•ˆμ „ν•˜μ§€ μ•Šλ‹€. Class κ°€ mutable properties λ₯Ό ν¬ν•¨ν•˜κ³ , 순차적 μ ‘κ·Ό(serialize access)을 ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄, μ„œλ‘œ λ‹€λ₯Έ Tasks 간에 Class 의 instance λ₯Ό 전달할 λ•Œ 예츑 λΆˆκ°€λŠ₯ν•˜κ³  잘λͺ»λœ κ²°κ³Όλ₯Ό 전달할 수 μžˆλ‹€(λ¬΄λΆ„λ³„ν•œ μˆœμ„œλ‘œ μ ‘κ·Όν•  경우 Reference Types 의 값이 μ˜λ„ν•œ μ‹œμ μ΄ μ•„λ‹Œλ°λ„ λΆˆκ΅¬ν•˜κ³  변경될 수 μžˆλ‹€).

이 문제λ₯Ό ν•΄κ²·ν•˜κΈ° μœ„ν•΄ μš°λ¦¬λŠ” Sendable Protocol 을 μ€€μˆ˜ν•˜λ„λ‘(conformance) μ„ μ–Έν•΄ Sendable Types둜 λ§Œλ“€ 수 μžˆλ‹€. Sendable Protocol 은 μ½”λ“œμ μΈ μš”κ΅¬μ‚¬ν•­(code requirements)은 μ—†μ§€λ§Œ, Swift κ°€ κ°•μ œν•˜λŠ” 의미둠적인 μš”κ΅¬μ‚¬ν•­(semantic requirements)이 μžˆλ‹€.


Sendable 의 μ„€λͺ…을 λ‹€μ‹œ μ½μ–΄λ³΄μž.

Sendable Types 의 값은 ν•˜λ‚˜μ˜ Concurrency domain μ—μ„œ λ‹€λ₯Έ Concurrency domain 으둜 μ•ˆμ „ν•˜κ²Œ 보낼 수 μžˆλ‹€. 예λ₯Ό λ“€μ–΄, Sendable Values λŠ” Actor 의 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œ arguments 둜 전달될 수 μžˆλ‹€. λ‹€μŒμ€ λͺ¨λ‘ Sendable 둜 ν‘œμ‹œ κ°€λŠ₯ν•˜λ‹€(marked as sendable).

  • Value Types
  • Reference types with no mutable storage
  • Reference types that internally manage access to their state
  • Functions and closures (by marking them with @Sendable)

μœ„μ—μ„œ 이미 μ‚΄νŽ΄λ³΄μ•˜λ“ μ΄ 이 ν”„λ‘œν† μ½œμ€ required methods λ‚˜ required properties 와 같은 μš”κ΅¬μ‚¬ν•­μ€ μ—†μ§€λ§Œ, `compile-time 에 κ°•μ œλ˜λŠ” 의미둠적인 μš”κ΅¬μ‚¬ν•­(semantic requirements)*이 μžˆλ‹€. 그리고 Sendable은 λ°˜λ“œμ‹œ Type이 μ„ μ–Έλœ 파일 λ‚΄μ—μ„œ μ„ μ–Έλ˜μ–΄μ•Όν•œλ‹€. μ΄λŸ¬ν•œ μš”κ΅¬μ‚¬ν•­μ— λŒ€ν•΄μ„œλŠ” μ•„λž˜ λ²ˆν˜Έμ— μ΄μ–΄μ„œ μ„€λͺ…ν•œλ‹€.

Compiler 의 κ°•μ œμ„± 없이 Sendable 을 μ„ μ–Έν•˜λ €λ©΄ Sendable protocol λŒ€μ‹  @unchecked Sendable protocol 을 μ±„νƒν•œλ‹€. 이 경우 정확성에 λŒ€ν•œ μ±…μž„μ΄ μ‚¬μš©μžμ—κ²Œ 있으며, μ‚¬μš©μžλŠ” lock λ˜λŠ” queueλ₯Ό μ΄μš©ν•΄ νƒ€μž…μ˜ μƒνƒœμ— λŒ€ν•œ λͺ¨λ“  접근을 λ³΄ν˜Έν•΄μ•Όν•œλ‹€. λ˜ν•œ 이 Unchecked conformance to Sendable은 Sendable 이 λ°˜λ“œμ‹œ Type 이 μ„ μ–Έλœ 파일 λ‚΄μ—μ„œ μ„ μ–Έλ˜μ–΄μ•Ό ν•œλ‹€λŠ” κ·œμΉ™ μ—­μ‹œ λ”°λ₯΄μ§€ μ•ŠλŠ”λ‹€.

3. Sendable Structures and Enumerations

Structures 와 Enumerations κ°€ Sendable Protocol 을 λ§Œμ‘±μ‹œν‚€κΈ° μœ„ν•΄ Sendable Members 와 Associated Values 만 κ°€μ Έμ•Όν•œλ‹€.

일뢀 μΌ€μ΄μŠ€μ˜ 경우 μ•”μ‹œμ μœΌλ‘œ Sendable 을 λ”°λ₯΄λŠ”데 그것은 λ‹€μŒκ³Ό κ°™λ‹€.

  • Frozen structures and enumerations
  • Structures and enumerations that aren’t public and aren’t marked @usableFromInline.

이 μ™Έ κ²½μš°λŠ” Sendable 에 λŒ€ν•œ 적합성을 λͺ…μ‹œμ μœΌλ‘œ μ„ μ–Έν•΄μ•Όν•œλ‹€.

Structures κ°€ nonsendable stored propertiesλ₯Ό 가지고 μžˆκ±°λ‚˜, Enumerations κ°€ nonsendable associated valuesλ₯Ό 가지고 μžˆλ‹€λ©΄ Sendable 적합성을 λ”°λ₯Ό 수 μ—†λ‹€. λ”°λΌμ„œ 이 경우 μœ„μ—μ„œ μ„€λͺ…ν–ˆλ“―이 @unchecked Sendableλ₯Ό ν‘œμ‹œν•΄ compile-time error λ₯Ό λΉ„ν™œμ„±ν™” ν•œ ν›„ μ‚¬μš©μžκ°€ 직접 ν•΄λ‹Ή Types κ°€ Sendable Protocol 의 의미둠적인 μš”κ΅¬μ‚¬ν•­(semantic requirements)을 λ§Œμ‘±ν•˜λŠ”μ§€ κ²€μ¦ν•΄μ•Όν•œλ‹€.

4. Sendable Actors

Actors λŠ” λͺ¨λ“  mutable state 에 순차적인 μ ‘κ·Όλ§Œ ν—ˆμš©ν•˜κΈ° λ•Œλ¬Έμ— μ•”μ‹œμ μœΌλ‘œ Sendable 을 λ§Œμ‘±ν•œλ‹€.

5. Sendable Classes

Classes κ°€ Sendable Protocol 을 λ”°λ₯΄κΈ° μœ„ν•΄μ„œλŠ” λ‹€μŒμ„ λ§Œμ‘±ν•΄μ•Όν•œλ‹€.

  • Be marked final
  • Contain only stored properties that are immutable and sendable
  • Have no superclass or have NSObject as the superclass

예λ₯Ό λ“€λ©΄ λ‹€μŒκ³Ό 같은 Classes λŠ” Swift 에 μ˜ν•΄ Sendable Protocol 을 채택해 적합성을 λ”°λ₯΄λ„둝 ν•  수 μžˆλ‹€.

final class Abc: Sendable {
    let x: String
    init(x: String) {
        self.x = x
    }
}


1 ) @MainActorκ°€ ν‘œμ‹œλœ ClassesλŠ” μ•”μ‹œμ μœΌλ‘œ Sendable을 λ§Œμ‘±ν•œλ‹€.

Main ActorλŠ” μžμ‹ μ˜ state 에 λŒ€ν•œ λͺ¨λ“  접근을 μ‘°μ •ν•˜κΈ° λ•Œλ¬Έμ— μ•”μ‹œμ μœΌλ‘œ Sendable 을 λ§Œμ‘±ν•˜λ©°, 이 Classes λŠ” mutable ν•˜κ³  nonsendableν•œ Stored Properties λ₯Ό μ €μž₯ν•  수 μžˆλ‹€.

2 ) Verify conform to sendable protocol manually

μœ„ 사항을 λ”°λ₯΄μ§€ μ•ŠλŠ” Classes λŠ” @unchecked Sendable을 ν‘œμ‹œν•˜κ³  μ‚¬μš©μžκ°€ 적합성을 λ§Œμ‘±ν•˜λŠ”μ§€ ν™•μΈν•œλ‹€.

class Abc: @unchecked Sendable {
    let x: String
    
    init(x: String) { self.x = x }
}

@unchecked Sendableλ₯Ό ν‘œμ‹œν•΄ compile-time error λ₯Ό λΉ„ν™œμ„±ν™” ν•œ ν›„ μ‚¬μš©μžκ°€ 직접 ν•΄λ‹Ή Types κ°€ Sendable Protocol 의 의미둠적인 μš”κ΅¬μ‚¬ν•­(semantic requirements)을 λ§Œμ‘±ν•˜λŠ”μ§€ κ²€μ¦ν•΄μ•Όν•œλ‹€.

6. Sendable Functions and Closures

Sendable Protocol 을 λ”°λ₯΄κ²Œ ν•˜λŠ” λŒ€μ‹  @Sendable attribute μ‚¬μš©ν•΄ Sendable Functions λ˜λŠ” Sendable Closures 을 λ‚˜νƒ€λ‚Ό 수 μžˆλ‹€.
λ‹Ήμ—°νžˆ μ „λ‹¬λ˜λŠ” Functions λ˜λŠ” Closures 의 λͺ¨λ“  값은 Sendable 을 λ§Œμ‘±ν•΄μ•Όν•œλ‹€. μΆ”κ°€λ‘œ ClosuresλŠ” 였직 Value 캑처만 μ‚¬μš©ν•΄μ•Όν•˜λ©°, κ·Έ 값은 λ°˜λ“œμ‹œ Sendable Type이어야 ν•œλ‹€.

Task.detached(priority:operation:) 호좜과 같이 Sendable Closures λ₯Ό μ˜ˆμƒν•˜λŠ” context μ—μ„œ μš”κ΅¬μ‚¬ν•­μ„ λ§Œμ‘±ν•˜λŠ” ν΄λ‘œμ €λŠ” μ•”μ‹œμ μœΌλ‘œ Sendable 을 λ§Œμ‘±ν•œλ‹€.

λ‹€μŒκ³Ό 같이 Type Annotation의 μΌλΆ€λ‘œ @Sendable을 ν‘œμ‹œν•˜κ±°λ‚˜ parameters 의 μ•žμ— @Sendable을 ν‘œμ‹œν•¨μœΌλ‘œ λͺ…μ‹œμ μœΌλ‘œ Sendable 을 λ§Œμ‘±ν•¨μ„ λ‚˜νƒ€λ‚Ό 수 μžˆλ‹€.

let sendableClosure = { @Sendable (number: Int) -> String in
    if number > 12 {
        return "More than a dozen."
    } else {
        return "Less than a dozen"
    }
}

7. Sendable Tuples

Sendable protocol 을 λ§Œμ‘±ν•˜κΈ° μœ„ν•΄μ„œλŠ” Tuples의 λͺ¨λ“  elements κ°€ Sendable 을 λ§Œμ‘±ν•΄μ•Όν•˜λ©°, 쑰건이 만쑱되면 Tuples μ—­μ‹œ μ•”μ‹œμ μœΌλ‘œ Sendable 을 λ§Œμ‘±ν•œλ‹€.

8. Sendable Metatypes

Int.Typeκ³Ό 같은 MetatypesλŠ” μ•”μ‹œμ μœΌλ‘œ Sendable을 λ§Œμ‘±ν•œλ‹€.

λ‹€μŒμ€ Intκ°€ μ–΄λ–»κ²Œ Sendable protocol 을 λ§Œμ‘±ν•˜λŠ”μ§€λ₯Ό 보여쀀닀.

extension Int: Sendable {}

λ”°λΌμ„œ λ‹€μŒκ³Ό 같은 Structure λŠ” μ•”μ‹œμ μœΌλ‘œ Sendable 을 λ§Œμ‘±ν•œλ‹€.

struct Abc {
    var xyz: Int
}

λ§Œμ•½ Genenric Type이 Sendable 을 λ§Œμ‘±ν•˜μ§€ μ•Šμ„ 경우 λ‹€μŒκ³Ό 같이 적합성을 λ”°λ₯΄λ„둝 ν•  수 μžˆλ‹€.

struct Box<Val: Sendable> {
    var abc: Val
}




Reference

  1. β€œConcurrency.” The Swift Programming Language Swift 5.7. accessed Jan. 05, 2023, Swift Docs Chapter 17 - Concurrency.
  2. β€œfor await…of.” MDN Web Docs. Dec. 14, 2022, accessed Jan. 10, 2023, MDN - for await…of.
  3. β€œPromise.all().” MDN Web Docs. Dec. 14, 2022, accessed Jan. 10, 2023, MDN - Promise.all().
  4. β€œTask.” Apple Developer Documentation. accessed Jan. 11, 2023, Apple Developer Documentation - Swift/Swift Standard Library/Concurrency/Task.
  5. β€œTaskGroup.” Apple Developer Documentation. accessed Jan. 11, 2023, Apple Developer Documentation - Swift/Swift Standard Library/Concurrency/TaskGroup.
  6. β€œcheckCancellation().” Apple Developer Documentation. accessed Jan. 11, 2023, Apple Developer Documentation - Swift/Swift Standard Library/../checkCancellation().
  7. β€œisCancelled.” Apple Developer Documentation. accessed Jan. 11, 2023, Apple Developer Documentation - Swift/Swift Standard Library/../isCancelled.
  8. β€œcancel().” Apple Developer Documentation. accessed Jan. 11, 2023, Apple Developer Documentation - Swift/Swift Standard Library/../cancel().
  9. β€œSendable.” Apple Developer Documentation. accessed Jan. 13, 2023, Apple Developer Documentation - Swift/Swift Standard Library/Sendable.
  10. β€œSendable and @Sendable in Swift.” Mobikul, Jul. 01, 2022, Mobikul - Sendable and @Sendable in Swift.