Concurrency

Groot·2022년 8월 24일
0

Swift Language Guide

목록 보기
17/24
post-thumbnail
post-custom-banner

Concurrency

  • Swift는 구조화된 방식으로 비동기 및 병렬 코드 작성을 지원합니다.
  • 비동기 코드는 일시 중단되었다가 나중에 다시 시작할 수 있지만 한 번에 프로그램의 한 부분만 실행됩니다.
  • 프로그램에서 코드를 일시 중단하고 다시 시작하면 UI 업데이트와 같은 단기 작업을 계속 진행하면서 네트워크를 통해 데이터를 가져오거나 파일을 구문 분석하는 것과 같은 장기 실행 작업을 계속할 수 있습니다.
  • 병렬 코드는 여러 코드 조각이 동시에 실행되는 것을 의미합니다. , 예를 들어 4코어 프로세서가 있는 컴퓨터는 각 코어가 하나의 작업을 수행하는 4개의 코드를 동시에 실행할 수 있습니다.
  • 병렬 및 비동기 코드를 사용하는 프로그램은 한 번에 여러 작업을 수행합니다.
  • 외부 시스템을 기다리는 작업을 일시 중단하고 이 코드를 메모리 안전한 방식으로 더 쉽게 작성할 수 있도록 합니다.
  • 병렬 또는 비동기 코드의 추가적인 스케줄링 유연성은 복잡성 증가 비용과 함께 제공됩니다.
  • Swift를 사용하면 일부 컴파일 시간 검사를 가능하게 하는 방식으로 의도를 표현할 수 있습니다 예를 들어 액터를 사용하여 변경 가능한 상태에 안전하게 액세스할 수 있습니다.
  • 그러나 동시성이 필요한 코드에서 동시성에 대한 Swift의 language-level support를 사용하면 Swift가 컴파일 시간에 문제를 포착하는 데 도움이 될 수 있습니다.
  • 이 장의 나머지 부분에서는 동시성이라는 용어를 사용하여 비동기 및 병렬 코드의 일반적인 조합을 나타냅니다.

    이전에 동시 코드를 작성한 적이 있다면 스레드 작업에 익숙할 것입니다.
    Swift의 동시성 모델은 스레드 위에 구축되지만 직접 상호 작용하지는 않습니다.
    Swift의 비동기 함수는 실행 중인 스레드를 포기할 수 있습니다.
    그러면 첫 번째 함수가 차단되는 동안 해당 스레드에서 다른 비동기 함수가 실행될 수 있습니다.
    비동기 함수가 재개될 때, Swift는 그 함수가 실행될 스레드에 대해 어떠한 보장도 하지 않습니다.

  • Swift의 언어 지원을 사용하지 않고 동시 코드를 작성하는 것이 가능하지만 해당 코드는 읽기가 더 어려운 경향이 있습니다.
  • 예를 들어 다음 코드는 사진 이름 목록을 다운로드하고 해당 목록의 첫 번째 사진을 다운로드하고 해당 사진을 사용자에게 표시합니다.
    listPhotos(inGallery: "Summer Vacation") { photoNames in
        let sortedNames = photoNames.sorted()
        let name = sortedNames[0]
        downloadPhoto(named: name) { photo in
            show(photo)
        }
    }
  • 이 간단한 경우에도 코드는 일련의 완료 처리기로 작성되어야 하므로 결국 중첩 클로저를 작성하게 됩니다.
  • 이 스타일에서는 깊은 중첩이 있는 더 복잡한 코드가 빠르게 다루기 어려워질 수 있습니다.

📌 Defining and Calling Asynchronous Functions

  • 비동기 함수 또는 비동기 메서드는 실행 도중에 일시 중단될 수 있는 특수한 종류의 함수 또는 메서드입니다.
  • 이는 완료될 때까지 실행되거나 오류가 발생하거나 반환되지 않는 일반 동기 함수 및 메서드와 대조됩니다.
  • 비동기 함수 또는 메서드는 여전히 이 세 가지 중 하나를 수행하지만 무언가를 기다리고 있을 때 중간에 일시 중지될 수도 있습니다.
  • 비동기 함수 또는 메서드의 본문 내부에서 실행을 일시 중단할 수 있는 각 위치를 표시합니다.
  • 함수 또는 메서드가 비동기임을 나타내려면 throw를 사용하여 throw 함수를 표시하는 방법과 유사하게 해당 선언에서 매개변수 뒤에 async 키워드를 작성합니다.
  • 함수 또는 메서드가 값을 반환하는 경우 반환 화살표(->) 앞에 async를 작성합니다.
  • 예를 들어 갤러리에 있는 사진의 이름을 가져오는 방법은 다음과 같습니다.
    func listPhotos(inGallery name: String) async -> [String] {
        let result = // ... some asynchronous networking code ...
        return result
    }
  • asynchronous 및 throwing 둘 다인 함수 또는 메서드의 경우 throw 전에 async를 작성합니다.
  • 비동기 메서드를 호출하면 해당 메서드가 반환될 때까지 실행이 일시 중단됩니다.
  • 호출이 중단될 가능성이 있는 지점을 표시하기 위해 호출부 앞에 await라고 씁니다.
  • 이것은 오류가 있는 경우 프로그램의 흐름에 가능한 변경 사항을 표시하기 위해 throw 함수를 호출할 때 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)
  • listPhotos(inGallery:) 및 downloadPhoto(named:) 함수는 모두 네트워크 요청을 수행해야 하므로 완료하는 데 비교적 오랜 시간이 걸릴 수 있습니다.
  • 반환 화살표 전에 async를 작성하여 둘 다 비동기식으로 만들면 이 코드가 그림이 준비될 때까지 기다리는 동안 앱의 나머지 코드가 계속 실행될 수 있습니다.
  • 위 예제의 동시성을 이해하기 위해 가능한 실행 순서는 다음과 같습니다.
    1. 코드는 첫 번째 줄에서 실행을 시작하고 첫 번째 await까지 실행됩니다. listPhotos(inGallery:) 함수를 호출하고 해당 함수가 반환될 때까지 기다리는 동안 실행을 일시 중단합니다.
    2. 이 코드의 실행이 일시 중단되는 동안 동일한 프로그램의 다른 동시 코드가 실행됩니다. 예를 들어 장기 실행 백그라운드 작업이 새 사진 갤러리 목록을 계속 업데이트할 수 있습니다. 이 코드는 await로 표시된 다음 일시 중단 지점까지 또는 완료될 때까지 실행됩니다.
    3. listPhotos(inGallery:)가 반환된 후 이 코드는 해당 지점에서 시작하여 계속 실행됩니다. 반환된 값을 photoNames에 할당합니다.
    4. sortedNames 및 name을 정의하는 행은 일반 동기 코드입니다. 이 줄에는 대기로 표시된 것이 없기 때문에 가능한 정지 지점이 없습니다.
    5. 다음 await는 downloadPhoto(named:) 함수에 대한 호출을 표시합니다. 이 코드는 해당 함수가 반환될 때까지 실행을 다시 일시 중지하여 다른 동시 코드에 실행할 기회를 제공합니다.
    6. downloadPhoto(named:)가 반환된 후 반환 값이 photo에 할당된 다음 show(_:)를 호출할 때 인수로 전달됩니다.
  • await로 표시된 코드의 가능한 일시 중단 지점은 비동기 함수 또는 메서드가 반환되기를 기다리는 동안 현재 코드 부분이 실행을 일시 중지할 수 있음을 나타냅니다.
  • 이것은 또한 스레드 양보(yielding)라고도 불리는데, 그 이유는 뒤에서 Swift가 현재 스레드에서 코드 실행을 일시 중단하고 대신 해당 스레드에서 다른 코드를 실행하기 때문입니다.
  • await가 있는 코드는 실행을 일시 중단할 수 있어야 하므로 프로그램의 특정 위치에서만 비동기 함수 또는 메서드를 호출할 수 있습니다.
    • 비동기 함수, 메서드 또는 속성의 본문에 있는 코드입니다.
    • @main으로 표시된 구조체, 클래스 또는 열거형의 정적 main() 메서드에 있는 코드입니다.
    • 아래 구조화되지 않은 동시성에 표시된 대로 구조화되지 않은 자식 작업의 코드입니다.
  • 가능한 일시 중단 지점 사이의 코드는 다른 동시 코드의 중단 가능성 없이 순차적으로 실행됩니다.
  • 예를 들어 아래 코드는 한 갤러리에서 다른 갤러리로 사진을 이동합니다.
    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:) 호출 사이에 다른 코드를 실행할 방법이 없습니다.
  • 그 시간 동안 첫 번째 사진이 두 갤러리에 모두 나타나며 앱의 불변성 중 하나가 일시적으로 중단됩니다.
  • 이 코드 덩어리가 앞으로 추가될 때까지 기다리면 안 된다는 점을 더욱 명확하게 하기 위해 해당 코드를 동기 함수로 리팩토링할 수 있습니다.
    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:) 함수는 동기식이므로 가능한 일시 중단 지점을 포함할 수 없음을 보장합니다.
  • 앞으로 이 함수에 동시 코드를 추가하려고 하면 가능한 일시 중단 지점을 도입하면 버그를 도입하는 대신 컴파일 시간 오류가 발생합니다.

    Task.sleep(until:clock:) 메서드는 동시성이 어떻게 작동하는지 알아보기 위해 간단한 코드를 작성할 때 유용합니다.
    이 메서드는 아무 작업도 수행하지 않지만 반환되기 전에 주어진 나노초 수 이상을 기다립니다.
    다음은 네트워크 작업 대기를 시뮬레이션하기 위해 sleep(until: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:) 함수는 배열의 모든 요소가 준비된 후 비동기적으로 전체 배열을 한 번에 반환합니다.

  • 또 다른 접근 방식은 비동기 시퀀스를 사용하여 한 번에 컬렉션의 한 요소를 기다리는 것입니다.

  • 비동기 시퀀스에 대한 반복은 다음과 같습니다.

    import Foundation
    
    let handle = FileHandle.standardInput
    for try await line in handle.bytes.lines {
        print(line)
    }
  • 일반적인 for-in 루프를 사용하는 대신 위의 예에서는 for뒤에 await를 씁니다.

  • 비동기 함수나 메서드를 호출할 때와 마찬가지로 await는 가능한 일시 중단 지점을 나타냅니다.

  • for-await-in 루프는 각 반복이 시작될 때, 다음 요소를 사용할 수 있을 때까지 잠재적으로 실행을 일시 중단합니다.

  • Sequence 프로토콜에 적합성을 추가하여 for-in 루프에서 고유한 유형을 사용할 수 있는 것과 같은 방식으로 AsyncSequence 프로토콜에 적합성을 추가하여 for-await-in 루프에서 고유한 유형을 사용할 수 있습니다.

📌 Calling Asynchronous Functions in Parallel

  • await를 사용하여 비동기 함수를 호출하면 한 번에 하나의 코드만 실행됩니다.

  • 비동기 코드가 실행되는 동안 호출자는 다음 코드 줄을 실행하기 위해 이동하기 전에 해당 코드가 완료될 때까지 기다립니다.

  • 예를 들어 갤러리에서 처음 세 장의 사진을 가져오려면 다음과 같이 downloadPhoto(named:) 함수에 대한 세 번의 호출을 기다릴 수 있습니다.

    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로 표시되지 않습니다.

  • 대신 사진이 정의된 라인까지 실행이 계속됩니다.

  • 이 시점에서 프로그램은 이러한 비동기 호출의 결과를 필요로 하므로 세 장의 사진이 모두 다운로드될 때까지 실행을 일시 중지하기 위해 await를 작성합니다.

  • 다음은 이 두 접근 방식의 차이점에 대해 생각할 수 있는 방법입니다.

    • 다음 줄의 코드가 해당 함수의 결과에 따라 달라지면 await를 사용하여 비동기 함수를 호출합니다. 이것은 순차적으로 수행되는 작업을 생성합니다.
    • 나중에 코드에서 결과가 필요하지 않을 때 async-let을 사용하여 비동기 함수를 호출합니다. 이렇게 하면 병렬로 수행할 수 있는 작업이 생성됩니다.
    • await와 async-let은 모두 일시 중단된 동안 다른 코드를 실행할 수 있도록 합니다.
    • 두 경우 모두 비동기 함수가 반환될 때까지 필요한 경우 실행이 일시 중지됨을 나타내기 위해 가능한 일시 중단 지점을 await로 표시합니다.
  • 동일한 코드에서 이 두 가지 접근 방식을 혼합할 수도 있습니다.

📌 Tasks and Task Groups

  • 작업은 프로그램의 일부로 비동기적으로 실행할 수 있는 작업 단위입니다.
  • 모든 비동기 코드는 일부 작업의 일부로 실행됩니다.
  • 이전 섹션에서 설명한 async-let 구문은 자식 작업을 생성합니다.
  • 작업 그룹을 생성하고 해당 그룹에 하위 작업을 추가할 수도 있습니다.
  • 그러면 우선 순위와 취소를 더 잘 제어할 수 있으며 동적 수의 작업을 생성할 수 있습니다.
  • 작업은 계층 구조로 정렬됩니다.
  • 작업 그룹의 각 작업에는 동일한 상위 작업이 있으며 각 작업에는 하위 작업이 있을 수 있습니다.
  • 작업과 작업 그룹 간의 명시적 관계 때문에 이 접근 방식을 구조적 동시성이라고 합니다.
  • 정확성에 대한 책임 중 일부는 사용자가 담당하지만 작업 간의 명시적 부모-자식 관계를 통해 Swift는 취소 전파와 같은 일부 동작을 처리할 수 있고 Swift는 컴파일 시간에 일부 오류를 감지할 수 있습니다.
    await withTaskGroup(of: Data.self) { taskGroup in
        let photoNames = await listPhotos(inGallery: "Summer Vacation")
        for name in photoNames {
            taskGroup.addTask { await downloadPhoto(named: name) }
        }
    }

📍 Unstructured Concurrency

  • 이전 섹션에서 설명한 동시성에 대한 구조화된 접근 방식 외에도 Swift는 구조화되지 않은 동시성을 지원합니다.
  • 작업 그룹의 일부인 작업과 달리 구조화되지 않은 작업에는 상위 작업이 없습니다.
  • 프로그램이 필요로 하는 방식으로 비정형 작업을 관리할 수 있는 완전한 유연성이 있지만 정확성에 대한 책임도 전적으로 사용자에게 있습니다.
  • 현재 액터에서 실행되는 비정형 작업을 생성하려면 Task.init(priority:operation:) 이니셜라이저를 호출하십시오.
  • 현재 액터의 일부가 아닌 구조화되지 않은 작업(더 구체적으로 분리된 작업으로 알려진)을 만들려면 Task.detached(priority:operation:) 클래스 메서드를 호출합니다.
  • 이 두 작업은 결과를 기다리거나 취소하는 것과 같이 상호 작용할 수 있는 작업을 반환합니다.
    let newPhoto = // ... some photo data ...
    let handle = Task {
        return await add(newPhoto, toGalleryNamed: "Spring Adventures")
    }
    let result = await handle.value

📍 Task Cancellation

  • Swift 동시성은 협력 취소 모델을 사용합니다.
  • 각 작업은 실행의 적절한 시점에서 취소되었는지 여부를 확인하고 적절한 방법으로 취소에 응답합니다.
  • 수행 중인 작업에 따라 일반적으로 다음 중 하나를 의미합니다.
    • CancellationError와 같은 오류 발생
    • nil 또는 빈 컬렉션 반환
    • 부분적으로 완료된 작업 반환
  • 취소를 확인하려면 작업이 취소된 경우 CancellationError를 발생시키는 Task.checkCancellation()을 호출하거나 Task.isCancelled의 값을 확인하고 자체 코드에서 취소를 처리합니다.
  • 예를 들어 갤러리에서 사진을 다운로드하는 작업은 부분 다운로드를 삭제하고 네트워크 연결을 닫아야 할 수 있습니다.
  • 취소를 수동으로 전파하려면 Task.cancel()을 호출하십시오.

📌 Actors

  • 작업을 사용하여 프로그램을 분리된 동시 조각으로 나눌 수 있습니다.

  • 작업은 서로 격리되어 있어 동시에 실행하는 것이 안전하지만 작업 간에 일부 정보를 공유해야 하는 경우가 있습니다.

  • 액터를 사용하면 동시 코드 간에 정보를 안전하게 공유할 수 있습니다.

  • 클래스와 마찬가지로 액터는 참조 유형이므로 클래스는 참조 유형에서 값 유형과 참조 유형의 비교는 클래스뿐만 아니라 액터에도 적용됩니다.

  • 클래스와 달리 액터는 한 번에 하나의 작업만 변경 가능한 상태에 액세스할 수 있도록 허용하므로 여러 작업의 코드가 액터의 동일한 인스턴스와 상호 작용하는 것이 안전합니다.

  • 예를 들어, 다음은 온도를 기록하는 액터입니다.

    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 키워드를 사용하여 액터를 소개하고 중괄호로 정의합니다.

  • TemperatureLogger 액터에는 액터 외부의 다른 코드가 액세스할 수 있는 속성이 있으며 액터 내부의 코드만 최대값을 업데이트할 수 있도록 max 속성을 제한합니다.

  • 구조체 및 클래스와 동일한 이니셜라이저 구문을 사용하여 액터의 인스턴스를 생성합니다.

  • 액터의 속성이나 메서드에 액세스할 때 await를 사용하여 잠재적 정지 지점을 표시합니다.

    let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
    print(await logger.max)
    // Prints "25"
  • 이 예에서 logger.max에 액세스하는 것은 가능한 정지 지점입니다.

  • 액터는 한 번에 하나의 작업만 변경 가능한 상태에 액세스할 수 있도록 허용하므로 다른 작업의 코드가 이미 로거와 상호 작용하고 있는 경우 이 코드는 속성 액세스를 기다리는 동안 일시 중단됩니다.

  • 대조적으로, 액터의 일부인 코드는 액터의 속성에 액세스할 때 작성하지 않습니다.

  • 예를 들어 다음은 새로운 온도로 TemperatureLogger를 업데이트하는 방법입니다.

    extension TemperatureLogger {
        func update(with measurement: Int) {
            measurements.append(measurement)
            if measurement > max {
                max = measurement
            }
        }
    }
  • update(with:) 메서드는 이미 액터에서 실행 중이므로 max와 같은 속성에 대한 액세스를 await로 표시하지 않습니다.

  • 이 방법은 또한 액터가 변경 가능한 상태와 상호 작용하기 위해 한 번에 하나의 작업만 허용하는 이유 중 하나를 보여줍니다.

  • 액터의 상태에 대한 일부 업데이트는 일시적으로 불변성을 깨뜨립니다.

  • TemperatureLogger 액터는 온도 목록과 최대 온도를 추적하고 새 측정값을 기록할 때 최대 온도를 업데이트합니다.

  • 업데이트 도중에 새 측정을 추가한 후 최대값을 업데이트하기 전에 온도 로거가 일시적으로 일치하지 않는 상태에 있습니다.

  • 여러 작업이 동일한 인스턴스와 동시에 상호 작용하는 것을 방지하면 다음 이벤트 시퀀스와 같은 문제를 방지할 수 있습니다.

    1. 코드는 update(with:) 메서드를 호출합니다. 먼저 측정 배열을 업데이트합니다.
    2. 코드에서 최대값을 업데이트하기 전에 다른 코드에서 최대값과 온도 배열을 읽습니다.
    3. 코드는 최대값을 변경하여 업데이트를 완료합니다.
  • 이 경우 다른 곳에서 실행 중인 코드는 데이터가 일시적으로 유효하지 않은 동안 update(with:) 호출 중간에 액터에 대한 액세스가 인터리브되어 잘못된 정보를 읽습니다.

  • Swift 액터를 사용할 때 이 문제를 방지할 수 있습니다. 해당 액터는 한 번에 해당 상태에 대해 하나의 작업만 허용하고 해당 코드는 wait가 정지 지점으로 표시되는 위치에서만 중단될 수 있기 때문입니다.

  • update(with:)는 중단 지점을 포함하지 않기 때문에 다른 코드는 업데이트 도중 데이터에 액세스할 수 없습니다.

  • 클래스의 인스턴스에서와 같이 액터 외부에서 이러한 속성에 액세스하려고 하면 컴파일 시간 오류가 발생합니다.

  • 예를 들어: print(logger.max) // Error

  • 액터의 속성이 해당 액터의 격리된 로컬 상태의 일부이기 때문에 await를 쓰지 않고 logger.max에 액세스하는 것은 실패합니다.

  • Swift는 액터 내부의 코드만 액터의 로컬 상태에 액세스할 수 있도록 보장합니다. 이 보증을 행위자 격리라고 합니다.

📌 Sendable Types

  • Ask 및 Actor를 사용하면 프로그램을 동시에 안전하게 실행할 수 있는 조각으로 나눌 수 있습니다.

  • 작업 또는 액터의 인스턴스 내부에서 변수 및 속성과 같이 변경 가능한 상태를 포함하는 프로그램 부분을 동시성 도메인이라고 합니다.

  • 일부 종류의 데이터는 변경 가능한 상태를 포함하기 때문에 동시성 도메인 간에 공유할 수 없지만 중복 액세스로부터 보호하지 못합니다.

  • 한 동시성 도메인에서 다른 동시성 도메인으로 공유할 수 있는 유형을 전송 가능 유형이라고 합니다.

  • 예를 들어 액터 메서드를 호출할 때 인수로 전달되거나 작업의 결과로 반환될 수 있습니다.

  • 이 장의 앞부분에 있는 예제에서는 동시성 도메인 간에 전달되는 데이터에 대해 항상 안전하게 공유할 수 있는 간단한 값 유형을 사용하기 때문에 전송 가능성에 대해 논의하지 않았습니다.

  • 대조적으로, 일부 유형은 동시성 도메인을 통과하는 것이 안전하지 않습니다.

  • 예를 들어 변경 가능한 속성을 포함하고 해당 속성에 대한 액세스를 직렬화하지 않는 클래스는 다른 작업 간에 해당 클래스의 인스턴스를 전달할 때 예측할 수 없고 잘못된 결과를 생성할 수 있습니다.

  • Sendable 프로토콜에 대한 준수를 선언하여 유형을 전송 가능한 것으로 표시합니다.

  • 이 프로토콜에는 코드 요구 사항이 없지만 Swift가 적용하는 의미론적 요구 사항이 있습니다. 일반적으로 유형을 보낼 수 있는 세 가지 방법이 있습니다.

  • 유형은 값 유형이고 변경 가능한 상태는 다른 전송 가능한 데이터(예: 전송 가능한 저장된 속성이 있는 구조 또는 전송 가능한 관련 값이 있는 열거)로 구성됩니다.

  • 유형에는 변경 가능한 상태가 없으며 변경 불가능한 상태는 읽기 전용 속성만 있는 구조나 클래스와 같은 다른 전송 가능한 데이터로 구성됩니다.

  • 이 유형에는 @MainActor로 표시된 클래스나 특정 스레드나 큐의 속성에 대한 액세스를 직렬화하는 클래스와 같이 변경 가능한 상태의 안전을 보장하는 코드가 있습니다.

  • 의미론적 요구 사항에 대한 자세한 목록은 Sendable 프로토콜 참조를 참조하세요.

  • 보낼 수 있는 속성만 있는 구조와 보낼 수 있는 관련 값만 있는 열거형과 같은 일부 유형은 항상 보낼 수 있습니다.

    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은 보낼 수 있는 속성만 있는 구조체이고 구조체가 public 또는 @usableFromInline으로 표시되지 않기 때문에 암시적으로 보낼 수 있습니다.

  • 다음은 Sendable 프로토콜에 대한 준수가 암시되는 구조의 버전입니다.

    struct TemperatureReading {
        var measurement: Int
    }
profile
I Am Groot
post-custom-banner

0개의 댓글