티스토리 뷰

Swift

Concurrency

은조공주 2023. 3. 7. 00:38
반응형

스위프트는 구조화된 방식으로 비동기적 / 병렬 코드를 작성할 수 있도록 지원합니다. Asynchronous code 란 중단되었다가 재개될 수 있고, 한 번에 하나의 프로그램만 실행됩니다. 프로그램에서 코드를 중단했다가 재개하는 것은, 네트워크를 통해 데이터를 가져오거나 / 파일을 파싱 하는 등의 장기적인 작업을 계속하면서, UI 업데이트와 같은 단기적 작업도 진행할 수 있게 해 줍니다. Pararell code 는 동시에 실행되는 여러 코드 조각들을 의미합니다. (예를 들어, 4 코어 프로세스가 있는 컴퓨터는 4개의 코드 조각을 동시에 실행할 수 있으며, 각 코어가 작업 중 하나를 수행합니다.) 비동기적 / 병렬 코드를 사용하는 프로그램은 (Asynchronous and pararell code) 한 번에 여러 작업을 수행하며, 외부 시스템을 기다리는 작업을 중단하고 memory-safe 한 방식으로 코드를 쉽게 작성할 수 있도록 합니다.

 

이 장의 나머지 부분에서는 Concurrency (동시성) 라는 용어를 사용하여 비동기 및 병렬 코드의 일반적인 조합을 참조합니다.

 

스위프트에서 제공하는 기본적 지원을 사용하지 않고 concurrent 한 코드를 작성할 수도 있지만, 그 코드는 아마 가독성이 떨어질 것입니다. 예를 들어, 다음의 코드는 사진 이름 목록을 다운로드하고, 해당 목록에서 첫 번째 사진을 다운로드하고, 그 사진을 유저에게 보여줍니다.

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

이런 간단한 케이스에서도, 코드가 completion handler 의 연속으로 이루어져 있기 때문에 nested closure 를 작성해야 합니다. 이런 방식으로는 더 복잡하고 nesting 이 깊은 코드는 빠르게 다루기 어려울 것입니다.

 

Defining and Calling Asynchronous Functions

비동기 함수, 또는 비동기 메소드 (Asynchronous function / method) 는 실행 도중 중단될 수 있는 특수한 종류의 함수 또는 메소드입니다. 이는 completion 까지 실행하고, error 를 throw 하고, return 하지 않는 일반적인 동기 함수 / 메소드 (Synchronous function / method) 와는 대조적입니다. 비동기 함수 또는 메소드는 여전히 이 세 가지 중 하나를 수행하지만, 무언가를 기다리며 중간에 중지될 수도 있습니다. 비동기 함수 / 메소드의 내부에서 우리는 실행이 중단될 수 있는 지점을 표시해 줄 수 있습니다.

 

함수 또는 메소드가 비동기임을 나타내려면, 기존에 throw 키워드를 사용해서 throwing 함수를 구현했던 것처럼 우리는 async 키워드를 사용할 수 있습니다. 함수 또는 메소드가 값을 리턴하면, 우리는 이 async 키워드를 return arrow (->) 전에 표시합니다. 예를 들어, 다음 코드는 갤러리에서 사진들의 이름 리스트를 불러오는 코드가 될 수 있습니다.

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

만약 두 키워드를 모두 사용해야 한다면, async 를 먼저 쓰고 throw 키워드를 씁니다.

비동기 메소드를 호출하면, 해당 메소드가 return 할 때까지 실행이 중단됩니다. 우리는 await 키워드를 해당 메소드 호출 전에 사용해, 중단 포인트를 표시할 수 있습니다. 이는 마치 throwing 함수를 호출하기 전 try 키워드를 사용해서 에러가 있으면 프로그램의 플로우가 바뀔 수 있다는 것을 알려주는 것과 유사합니다. 비동기 메소드 내부에서의 실행 플로우는, 다른 비동기 메소드를 호출할 때만 중단됩니다 - 모든 중단 지점 (suspension point)await 키워드로 마킹됩니다.

예를 들어, 다음 코드는 갤러리에서 모든 사진의 이름 리스트를 가져와 첫 번째 사진을 보여줍니다.

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

listPhotos(inGallery:)downloadPhoto(named:) 함수는 모두 네트워크 리퀘스트를 생성하기 때문에, 완료되는 데에 상대적으로 더 오래 걸릴 수 있습니다. 이 두 함수의 return arrow 앞에 async 키워드를 사용하여 모두 비동기 함수로 만들면, 사진이 준비될 때까지 기다리는 동안 앱의 나머지 코드가 계속 실행될 수 있습니다.

위 예제의 concurrent 특성을 이해하기 위해, 다음 실행 순서를 가지고 설명하겠습니다.

1. 코드는 첫 번째 라인부터 실행되기 시작해서, 첫 번째 await 까지 실행됩니다. 여기서 listPhotos(inGallery:) 함수를 호출하고, 이 함수가 리턴할 때까지 기다리는 동안 실행을 중단합니다.

2. 코드의 실행이 중단되는 동안, 동일한 프로그램 내의 다른 concurrent 코드가 실행됩니다. 예를 들어서, 장기간 실행되는 백그라운드 작업이 새로운 포토 갤러리들의 목록을 계속 업데이트할 수 있습니다. 이 코드는 await 으로 마킹된 다음 정지 지점까지, 혹은 완료될 때까지 실행됩니다.

3. listPhotos(inGallery:) 가 리턴한 후, 이 코드는 해당 지점부터 실행을 계속합니다. 리턴된 값이 photoNames 에 할당됩니다.

4. sortedNames, name 에 값을 할당하는 2, 3라인은 일반적인 동기 코드입니다. await 으로 마킹된 지점이 없기 때문에, 여기에서는 정지 지점이 없습니다.

5. 다음 await 마크는 downloadPhoto(named:) 함수의 호출 지점입니다. 이 코드는 해당 함수가 리턴할 때까지 실행을 중지하고, 다른 concurrent 코드에게 실행될 기회를 줍니다.

6. downloadPhoto(named:) 가 리턴하면, 이 리턴값이 photo 에 할당되고, show(_:) 메소드를 호출할 때 argument 로 전달됩니다.

코드에서 await 으로 표시된 중단 지점은 현재 코드 조각이 비동기 함수 혹은 메소드가 리턴할 때까지 기다리면서 실행을 중단할 수 있음을 뜻합니다. 이를 yielding the thread 라고도 하는데, Swift 가 현재 스레드에서 코드 실행을 중단하고 대신 이 스레드에서 다른 코드를 대신 실행하기 때문입니다. await 을 사용한 코드는 실행을 중지할 수 있어야 하므로, 프로그램의 특정 위치에서만 비동기 함수 / 메소드를 호출할 수 있습니다. :

- 비동기 함수, 메소드, 혹은 프로퍼티의 내부 코드 안에서

- @main 키워드로 마킹된, struct / class / 혹은 enum 의 static main() 코드 안에서

- 아래 Unstructured Concurrency 에서 설명할, 구조화되지 않은 하위 태스크( unstructured child task )의 코드 안에서

중단 포인트 사이의 코드는 다른 concurrent code 에서 중단될 가능성 없이 순차적으로 실행됩니다. 예를 들어, 다음 코드는 한 갤러리에서 다른 갤러리로 사진을 이동합니다.

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:) 호출 사이에 다른 코드가 실행될 수 있는 방법은 없습니다. 이 시점에, 첫 번째 사진은 두 갤러리에 모두 나타나 앱의 불변성이 깨지게 됩니다. 앞으로 이 코드 청크에 await 이 추가되지 않아야 한다는 것을 더욱 명확히 하기 위해, 해당 코드를 다음 동기 함수로 리팩토링할 수 있습니다. :

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

위의 예시 코드에서는, move(_:from:to:) 함수가 동기적이기 때문에, 여기에는 중단 포인트를 포함하고 있지 않음을 보장할 수 있습니다. ( async 키워드를 포함하고 있지 않아서 알 수 있음 )

앞으로, 이 함수에 concurrent 한 코드를 추가해 중단 포인트를 넣으려고 하면, 컴파일 오류가 발생할 것입니다.

Task.sleep(until:tolerance:clock:) 메소드는 Concurrency 가 동작하는 방식을 공부하며 간단한 코드를 작성할 때 유용합니다. 이 메소드는 아무 동작도 하지 않지만, 적어도 지정된 나노초 동안 기다렸다가 다시 시작합니다. 다음은 sleep(until:tolerance:clock:) 을 사용하여 네트워크 작업 대기를 시뮬레이션하는 버전의 listPhotos(inGallery:) 입니다.
func listPhotos(inGallery name: String) async throws -> [String] {
    try await Task.sleep(until: .now + .seconds(2), clock: .continuous)
    return ["IMG001", "IMG99", "IMG0404"]
}​

 

Asynchronous Sequences

이전 섹션의 listPhotos(inGallery:) 함수는 배열의 모든 element 가 준비된 후에 전체 배열을 한 번에 비동기적으로 반환합니다. 또 다른 접근법은, 비동기 시퀀스를 이용하여 한 번에 하나의 element 를 기다리는 것입니다. 다음은 비동기 시퀀스를 반복하는 코드입니다. :

import Foundation

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

위의 예시 코드는 일반적인 for-in loop 를 사용하는 대신, for 뒤에 await 을 사용합니다. 비동기 함수 혹은 메소드를 호출할 때와 마찬가지로, await 사용은 중단 가능성이 있음을 나타냅니다. for-await-in loop 는 각 반복이 시작될 때 잠재적으로 실행을 중단하고, 다음 element 를 사용할 수 있기를 기다립니다.

Sequence 프로토콜에 conformance (?) 를 추가하여 for-in loop 에서 우리의 own type 을 사용할 수 있는 것처럼, AsyncSequence 프로토콜에서도 conformance 를 추가하여 for-await-in loop 에서 우리의 own type 을 사용할 수 있습니다.

 

Calling Asynchronous Functions in Parallel

await 으로 비동기 함수를 호출하면, 한 번에 하나의 코드만 실행됩니다. 비동기 코드가 실행되는 동안, caller 는 코드가 완료될 때까지 기다렸다가 다음 줄의 코드를 실행합니다. 예를 들어, 갤러리에서 처음 세 장의 사진을 가져오려면, 다음과 같이 downloadPhoto(named:) 함수에 대한 세 번의 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)

이러한 접근법에는 아주 중요한 결점이 있습니다: 다운로드가 비동기적이므로, 다른 작업이 진행되는 동안에도 한 번에 하나의 downloadPhoto(named:) 호출만 실행됩니다. 각 사진은 다음 사진이 다운로드되기 전에 완전히 다운로드 됩니다. 그러나, 각 사진을 개별적으로 다운로드하거나 동시에 다운로드할 수 있는데 굳이 이렇게 기다릴 필요가 없습니다.

비동기 함수를 호출하여 주변 코드와 병렬로 실행하려면, 상수를 선언할 때 let 앞에 async 를 쓴 다음 상수를 사용할 때마다 await 을 사용하면 됩니다.

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)

이 예시에서는, downloadPhoto(named:) 에 대한 세 번의 호출 모두 이전 호출이 완료될 때까지 기다리지 않고 시작합니다. 사용 가능한 시스템 리소스가 충분하면, 동시에 실행될 수도 있습니다. 이 코드들이 함수의 결과를 기다리기 위해 중단되지 않기 때문에, 이 세 번의 호출은 모두 await 으로 표시될 필요가 없습니다. 대신, photos 가 정의된 라인까지 실행이 계속됩니다. 이 시점에서 프로그램은 이러한 비동기 호출들의 결과를 필요로 하기 때문에, 세 개의 사진이 모두 다운로드될 때까지 실행을 중단하기 위해 await 키워드를 사용합니다.

다음은 이러한 두 가지 접근 방식의 차이점을 생각해 볼만한 것들입니다. :

- 다음 줄의 코드가 해당 함수의 결과에 따라 달라지는 경우, await 을 사용하여 비동기 함수를 호출합니다. 이렇게 하면 순차적으로 수행되는 작업이 생성됩니다.

- 코드에서 조금 나중까지 결과가 필요하지 않을 때는 async-let 을 사용하여 비동기 함수를 호출합니다. 이렇게 하면 병렬로 수행될 수 있는 작업이 생성됩니다.

- awaitasync-let 모두 중단된 동안 다른 코드를 실행할 수 있습니다.

- 두 가지 경우 모두, 가능한 중단 지점을 await 으로 표시하여 필요한 경우 비동기 함수가 반환될 때까지 실행이 중지됨을 나타냅니다.

동일한 코드에서 이 두 가지 접근 방법을 혼합하여 사용할 수도 있습니다.

 

Task and Task Groups

태스크 - task 는 프로그램의 일부로, 비동기적으로 실행될 수 있는 작업의 단위입니다. 모든 비동기 코드는 어떠한 task 의 일부로 실행됩니다. 이전 섹션에서 설명한 async-let 구문을 사용하면 하위 태스크가 생성됩니다. 또한 태스크 그룹을 생성하고, 해당 그룹에 하위 태스크를 추가하여 우선 순위나 취소를 효과적으로 컨트롤하고 동적인 갯수의 태스크를 생성할 수 있습니다.

태스크는 계층 구조로 정렬됩니다. 태스크 그룹의 각 테스크는 동일한 부모 태스크를 가지며, 각각의 태스크는 자식 태스크들을 가집니다. 태스크와 태스크 그룹 간의 명시적인 관계 때문에, 이 접근 방식을 구조적 동시성 - structured concurrency 라고 합니다. 

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

태스크 그룹에 대한 더 많은 정보는 이 곳을 참고 -> https://developer.apple.com/documentation/swift/taskgroup

 

Unstructured Concurrency

이전 섹션에서 설명한 concurrency 에 대한 구조화된 접근 방식 외에도, Swift 는 구조화되지 않은 concurrency 를 지원합니다. 태스크 그룹의 일부인 태스크와 달리, 구조화되지 않은 (비정형) 태스크에는 상위 태스크가 없습니다. 프로그램에 필요한 모든 방식으로, 비정형 태스크를 관리할 수 있는 완전한 유연성이 있지만 정확성에 대해서도 전적으로 책임이 있습니다. 현재 actor 에서 실행되는 비정형 태스크를 생성하려면, Task.init(priority:operation:) 이니셜라이저를 호출할 수 있습니다. 현재 actor 의 일부가 아닌, (detached task 라고 알려져 있는) 비정형 태스크를 생성하려면, Task.detached(priority:operation:) 클래스 메소드를 호출합니다. 이러한 두 작업들은 모두 우리가 결과를 기다리거나 취소하는 등으로 상호 작용할 수 있는 태스크를 반환합니다.

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

Detached task 에 대한 더 자세한 정보 -> Task.

 

Task Cancellation

Swift Concurrency 는 cooperative cancellation model 을 사용합니다. 각각의 태스크는 실행 중 적절한 시점에서 취소되었는지 여부를 확인하고, 적절한 방식으로 취소에 반응합니다. 수행 중인 작업이 무엇이었는지에 따라, 이는 일반적으로 다음 중 하나를 의미합니다.

- CancellationError 등의 오류를 throw

- nil 혹은 빈 배열을 리턴

- 부분적으로 완료된 작업을 리턴

취소 (Cancellation) 를 확인하려면, Task.checkCancellation() (태스크가 취소되었을 경우 CancellationError 를 throw) 를 호출하거나, 혹은 Task.isCancelled 값을 확인하고 코드로 처리합니다.

예를 들어서, 갤러리에서 사진을 다운로드하는 작업은 부분적으로 다운로드된 것들을 삭제하고 네트워크 커넥션을 닫아야 할 수도 있습니다. 수동으로 태스크를 취소하려면, Task.cancel() 를 호출하면 됩니다.

 

Actors

태스크를 사용하여 프로그램을 독립적이고, 동시성이 있는 (concurrent 한) 조각들로 나눌 수 있습니다. 태스크들은 서로 독립적이어서 동시에 실행하는 것이 안전하지만, 때때로 태스크간에 어떤 정보를 공유해야 할 때도 있습니다. Actor 를 사용하면 동시 코드 (concurrent code) 간에 정보를 안전하게 공유할 수 있습니다.

Class 처럼, Actor 도 참조 타입이어서 Classes Are Reference Types 에서의 값 타입과 참조 타입의 비교가 Class 뿐만 아니라 Actor 에도 적용됩니다. 하지만 Class 와 달리 Actor 는 한 번에 하나의 작업만 mutable 한 상태로 접근할 수 있으므로, 여러 태스크의 코드가 Actor 의 동일한 인스턴스와 상호 작용하는 것이 안전합니다. 예를 들어서, 온도를 기록하는 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
    }
}

actor 키워드와 중괄호 한쌍을 사용하여 Actor 를 정의할 수 있습니다. TemperatureLogger Actor 에는 Actor 외부의 다른 코드가 접근할 수 있는 프로퍼티를 가지며, Actor 내부의 코드만 최대값을 업데이트할 수 있도록 max 프로퍼티를 private(set) 으로 제한합니다.

구조체와 클래스와 동일한 생성자 문법으로 Actor 의 인스턴스를 만들 수 있습니다. Actor 의 프로퍼티나 메소드에 접근할 때, await 키워드를 사용하여 잠재적인 중단 포인트를 표시할 수 있습니다. 예를 들어 >

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

위의 예시에서, logger.max 에의 접근은 잠재적(가능한) 중단 지점입니다. Actor 는 mutable 한 상태에서 (변경 가능한 상태에서), 한 번에 하나의 태스크만 접근하도록 허용하기 때문에 다른 태스크의 코드가 이미 logger 와 상호 작용하는 경우 이 코드는 프로퍼티에 접근하기 위해서 대기하는 동안 일시 중단됩니다.

반대로, Actor 의 내부 코드는 Actor 의 프로퍼티에 접근할 때 await 키워드를 사용하지 않습니다. 예를 들어, TemperatureLogger 를 새로운 온도로 업데이트하는 방법은 다음과 같습니다 >

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

update(with:) 메소드는 이미 Actor 에서 실행 중이므로, max 에 대한 접근에서 await 키워드를 사용할 필요가 없습니다. 또한 이 메소드는 Actor 가 한 번에 하나의 태스크만 mutable 한 상태에서 상호 작용을 할 수 있도록 허용하는 이유 중 하나를 보여주기도 합니다. Actor 상태에 대한 업데이트는 일시적으로 불변성을 깨뜨립니다. TemperatureLogger Actor 는 온도 배열과 최대 온도를 트래킹하고, 새로운 측정값을 기록할 때 최대 온도를 업데이트합니다. 업데이트 도중에 새로운 측정값을 추가한 후 최대값을 업데이트하기 전에 온도 로거가 일시적으로 일관적이지 않은 상태에 있습니다. 여러 태스크가 동일한 인스턴스에 동시에 상호 작용하는 것을 막으면 다음과 같은 문제들을 방지할 수 있습니다 :

1. 코드에서 update(with:) 메소드를 호출하고, 측정온도 배열을 먼저 업데이트합니다.

2. 코드에서 max 값을 업데이트하기 전에, 다른 곳의 코드에서 최대값과 온도 배열을 읽습니다.

3. 코드는 최대값을 변경하여 업데이트를 완료합니다.

이 경우, 다른 곳에서 실행 중인 코드가 잘못된 정보를 읽게 되는데, 이는 데이터가 일시적으로 유효하지 않은 동안 update(with:) 호출 도중 Actor 에 대한 접근 권한이 interleave 되었기 때문입니다. Swift actor 는 한 번에 하나의 태스크만 허용하고, await 으로 중단 지점을 표시하는 경우에만 해당 코드를 중단할 수 있기 때문에 이러한 문제를 방지할 수 있습니다. update(with:) 에는 중단 지점을 표시하는 부분이 없기 때문에, 업데이트 도중 다른 코드가 데이터에 접근할 수 없습니다.

클래스의 인스턴스처럼, 액터 외부에서 이러한 프로퍼티에 접근하려고 하면 컴파일 오류가 발생합니다. 예를 들어 :

print(logger.max)  // Error

액터의 프로퍼티들이 해당 액터의 독립적인 로컬 상태의 일부이기 때문에, await 키워드를 사용하지 않고 logger.max 에 접근하면 실패합니다. Swift 는 액터 내부의 코드만 액터의 로컬 상태에 접근할 수 있음을 보장합니다. 이러한 내용은 actor isolation 으로 알려져 있습니다.

 

Sendable Types

태스크와 액터를 사용하면, 프로그램을 안전하게 동시에 실행할 수 있는 조각들로 나눌 수 있습니다. 태스크 혹은 액터의 인스턴스 내부에서 변수나 프로퍼티 등 변경 가능한 상태를 포함하는 부분을 Concurrency domain (동시성 도메인) 이라고 합니다. 어떤 종류의 데이터는 mutable 한 상태를 가지지만, 중복 접근으로부터 보호되지 않기 때문에 동시성 도메인 간에 공유할 수 없습니다.

하나의 동시성 도메인에서 다른 도메인으로 공유할 수 있는 유형을 Sendable type 이라고 합니다. 예를 들어서, actor 메소드를 호출할 때 인자로 전달되거나, 태스크의 결과로 반환될 수 있습니다. 본 글 앞부분의 예제에서는, 동시성 도메인 간에 전달되는 데이터가 항상 안전하게 공유될 수 있는 단순 값 타입 데이터였기 때문에 sendability 에 대해서는 논의하지 않았습니다. 반대로, 어떤 타입은 동시성 도메인 간에 전송될 때 안전하지 않습니다. 예를 들어, mutable 한 프로퍼티들을 가지고 있으면서 해당 프로퍼티에 대한 접근을 serialize 하지 않은 클래스는 해당 클래스의 인스턴스를 다른 태스크간에 전달할 때 예측할 수 없고, 잘못된 결과를 생성할 수 있습니다.

Sendable 프로토콜을 따르도록 하여 타입을 sendable 하게 만들 수 있습니다. 이 프로토콜은 별도의 코드 requirement 는 없지만, Swift 가 강제하는 의미론적 요구사항이 있습니다. 일반적으로, 타입을 sendable 로 만드는 데에는 세 가지 방법이 있습니다.

1. 타입은 값 타입이어야 하고, mutable 한 상태는 다른 sendable 한 데이터로 구성되어야 합니다 - 예를 들어, sendable 한 stored property 를 가진 구조체 혹은 sendable 한 연관값을 가진 enum

2. mutable 한 상태를 가지지 않으며, immutable 한 상태가 다른 sendable 한 데이터로 구성되어 있을 때 - 예를 들어, read-only 프로퍼티만 가지는 구조체 혹은 클래스

3. mutable 한 상태에서의 안전성을 보장하는 코드를 가진 타입 - 예를 들어, @MainActor 키워드로 표시된 클래스 혹은 특정 쓰레드나 큐에서 프로퍼티에 대한 접근을 serialize 하는 클래스

의미론적 요구 사항에 대한 자세한 내용은 - Sendable 프로토콜 참조!

어떤 타입은 항상 sendable 합니다. 예를 들어 : (sendable 한 프로퍼티와, sendable 한 연관값을 가진 enum 만 가지는 구조체)

struct TemperatureReading: Sendable {
    var measurement: Int
}

extension TemperatureLogger {
    func addReading(from reading: TemperatureReading) {
        measurements.append(reading.measurement)
    }
}

let logger = TemperatureLogger(label: "Tea kettle", measurement: 85)
let reading = TemperatureReading(measurement: 45)
await logger.addReading(from: reading)

TemperatureReading 은 sendable 한 프로퍼티만 가지는 구조체이기 때문에, 그리고 이 구조체가 public 이나 @usableFromInline 으로 마크되어있지 않기 때문에, 이 구조체는 암묵적으로 sendable 합니다.

 

# 원문 :

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함