Swift.org 사이트의 강좌 번역본입니다.
원문 : 애플 개발 문서 Swift 5.7 Language Guide - Concurrency
동시성(Concurrency)
Swift는 구조화된 방식으로 비동기와 병렬 코드 작성하는 것을 지원합니다. 비동기 코드(Asynchronous code)는 일시중지하고 나중에 재개할 수 있수 있지만, 한 번에 프로그램의 한 부분만 실행합니다. 프로그램에서 코드를 일시중지하고 재개하면 UI 업데이트 같은 단기(short-term) 작업을 계속할 수 있으며, 네트워크를 통해서 데이터를 가져오거나 파일을 분석하는것 같은 장기(long-running) 작업을 계속할 수 있습니다. 병렬 코드(Parallel code)는 여러 코드가 동시에 실행하는 것을 의미합니다 - 예를들어, 4 코어(four-core) 프로세서를 탑재한 컴퓨터는 동시에 4개의 코드를 실행할 수 있으며, 각각의 코어가 하나의 작업을 처리합니다. 병렬과 비동기 코드를 사용하는 프로그램은 한번에 여러 작업을 처리합니다. 외부 시스템을 기다리는 작업을 일시중지하고 메모리에 안전한 방법으로 코드를 더 쉽게 작성하도록 합니다.
병렬 또는 비동기 코드로 인한 추가적인 스케쥴링 유연성은 복잡성을 증가시키게 됩니다. Swift는 컴파일 시간을 확인할수 있다는 의도(intent)를 표현할수 있습니다. - 예를들어, actors를 사용해서 가변 상태(mutable state)를 사용할 수 있습니다. 하지만, 느리거나 버그가 있는 코드에 동시성(concurrency)을 추가한다고해서, 빨라지거나 오류가 수정되는 것을 보장하지 않습니다. 실제로, 동시성(concurrency)를 추가하면 디버깅하기가 더 어려워질 수 있습니다. 하지만, 동시성(concurrency)이 필요한 코드에서 동시성을 위해 Swift 언어 레벨로 지원하는 것을 사용하는 것은 Swift가 컴파일 시간에 문제를 발견하는데 도움이 될수 있다는 것을 의미합니다.
이번 장(chapter)의 나머지 부분에서 비동기(asynchronous)와 병렬 코드의 공통 결합(combination)을 가리키기 위해 동시성(concurrency)이라는 용어를 사용합니다.
주의 이전에 동시성(concurrent) 코드를 작성한 적이 있는 경우에, 스레드로 작업하는것이 익숙할 것입니다. Swift에서의 동시성 모델은 스레드 위에서 만들어지지만, 직접적으로 상호작용을 하지 않습니다. Swift에서의 비동기 함수는 실행중인 스레드를 멈출수 있으며, 첫번째 함수가 차단되는 동안에 해당 스레드에서 다른 비동기 함수가 실행될 수 있습니다.
비록, Swift의 언어에서 지원하는 것을 사용하지 않고 동시(concurrent) 코드를 작성할 수 있지만, 해당 코드가 읽기 더 어려운 경향이 있습니다. 예를들어, 다음에 오는 코드는 사진 이름의 목록을 다운로드하고 목록에 있는 첫번째 사진을 다운로드해서 사용자에게 그 사진을 보여줍니다.
listPhotos(inGallery: "Summer Vacation") { photoNames in
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
downloadPhoto(named: name) { photo in
show(photo)
}
}
이런 간단한 경우에도 코드가 일련(series)의 완료 핸들러로 작성되어야 하므로 중첩된 클로져(closures)로 작성하게 됩니다. 이 스타일에서, 깊은 중첩으로 된 더 복잡한 코드는 쉽게(quickly) 다루기 어려워질수 있습니다.
비동기 함수 정의하고 호출하기(Defining and Calling Asynchronous Functions)
비동기 함수(asynchronous function) 또는 비동기 메소드(asynchronous method)는 실행 도중에(partway) 보류될(suspended) 수 있는 특별한 종류의 함수나 메소드 입니다. 이는 완료를 실행하거나 오류를 던지거나 반환을 하지 않는, 일반적인 동기함수와 메소드와는 데조적입니다. 비동기 함수나 메소드는 이러한 3가지 중에 하나를 하지만, 이 또한 무언가를 기다릴때 중간에서 일시정지할 수 있습니다. 비동기 함수나 메소드의 본문 안쪽에서 실행이 보류될(suspended) 수 있는 각 위치를 표시합니다.
함수나 메소드가 비동기 인것을 나타내기 위해, 해당 매개변수 뒤에 async 키워드를 선언하며, 이는 오류를 던지는 함수를 표현하기 위해 throws를 사용하는 것과 비슷합니다. 함수나 메소드가 값을 반환하는 경우에, 반환하기 전 화살표(->) 앞에 async를 작성합니다. 예를들어, 다음은 갤러리에 있는 사진들의 이름을 가져오는 방법입니다.
func listPhotos(inGallery name: String) async -> [String] {
let result = // ... some asynchronous networking code ...
return result
}
함수나 메소드가 비동기와 오류 던지기 둘다인 경우에, throws 앞에 async를 작성합니다.
비동기 메소드를 호출할때, 메소드가 반환할때까지 실행을 보류(supends)시킵니다. 보류될수 있다는 것을 표시하기 위해 호출하는 곳 앞에 await를 작성합니다. 오류가 있는 경우에 프로그램의 흐름이 바뀔수 있다는 것을 표시하기 위해, 오류를 던지는 함수를 호출할때 try를 사용하는 것과 같습니다. 비동기 메소드의 내부에서의 실행 흐름은 다른 비동기 메소드를 호출할때만 보류됩니다 - 보류하는 것은 결코 암시적(implicit)이거나 우선적(preemptive)이지 않습니다 - 즉, 모든 보류 가능한 지점을 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를 작성해서 비동기적으로 만들어 해당 코드가 사진이 준비될때까지 앱의 나머지 코드를 계속 실행할 수 있습니다.
위 예제의 동시 특성(concurrent nature)을 이해하기 위해서, 다음과 같은 순서로 실행이 가능합니다.
- 해당 코드는 첫번째 줄에서 시작해서 첫번째 await까지 실행합니다. listPhotos(inGallery:) 함수를 호출하고 해당 함수가 반환 할때까지 기다리는 동안에 실행을 보류합니다.
- 해당 코드의 실행이 보류되는 동안에, 같은 프로그램에 있는 다른 동시 코드가 실행합니다. 예를들어, 긴 시간동안 새로운 사진 갤러리의 목록을 업데이트를 계속하는 백그라운드 작업일수도 있습니다. 또한, 해당 코드는 await로 표시된 다음 보류시점까지 또는 완료될때까지 실행합니다.
- listPhotos(inGallery:) 반환 이후에, 해당 코드는 그 시점부터 실행을 계속합니다. photoNames에 반환된 값을 할당합니다.
- sortedNames와 name을 정의하는 줄은 일반적인 동기식 코드입니다. 해당 줄에 await로 표시된 것이 없기 때문에, 보류하는 시점이 없습니다.
- 다음 await는 downloadPhoto(named:) 함수를 호출하는 것을 표시합니다. 이 코드는 해당 함수가 반환할때까지 실행을 일시정지하고, 다른 동시 코드에게 실행할 기회(opportunity)를 줍니다.
- downloadPhoto(named:) 반환 이후에, 반환 값은 photo에 할당하고나서 show(_:) 호출할때 인자로 전달됩니다.
await로 표시된 코드의 보류 시점은 비동기 함수나 메소드가 반환하는 것을 기다리는 동안에 현재 코드 부분 실행이 일시정지 될수 있다는 것을 나타냅니다. 또한, 이를 스레드 양보(yielding the thread)라고 하기 때문에, 화면 뒤에서, Swift는 현재 스레드에서 코드 실행을 보류하고 대신에 해당 스레드에 있는 다른 코드를 실행합니다. await인 코드는 실행을 보류할수 있어야 하므로, 프로그램의 특정 위치에서만 비동기 함수나 메소드를 호출할 수 있습니다.
- 비동기 함수, 메소드, 프로퍼티의 본문(body)에 있는 코드
- @main으로 표시된 구조체, 클래스, 열거형의 static main() 메소드에 있는 코드
- 아래의 구조화되지 않은 동시(Unstructured Concurrency)에서 볼수 있는 것처럼, 분리된 하위작업(child task)에 있는 코드,
주의 Task.sleep(_:) 메소드는 동시 작업하는 것을 배우기 위해 간단한 코드를 작성할때 유용합니다. 해당 메소드는 아무것도 하지 않지만 반환되기 전에 주어진 지정된 시간(나노초)만큼 기다립니다. 다음은 네트워크 작업을 기다리는 것을 시뮬레이션하기 위해 sleep()를 사용한 listPhotos(inGallery:) 함수를 버젼입니다.
func listPhotos(inGallery name: String) async -> [String] { await Task.sleep(2 * 1_000_000_000) // Two seconds return ["IMG001", "IMG99", "IMG0404"] }
비동기 시퀀스(Asynchronous Sequences)
이전 섹션에서의 listPhotos(inGallery:) 함수는 배열의 모든 요소들이 준비가 된 후에 비동기적으로 전체 배열을 한번에 반환합니다. 또 다른 방법(approach)은 비동기 시퀀스(asynchronous sequence)를 사용해서 한번에 하나의 컬렉션(collection) 요소를 기다리는 것입니다. 다음은 비동기 시퀀스(asynchronous sequence)를 반복(iterating)하는 것입니다.
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를 사용해서 비동기 함수를 호출하면 한번에 하나의 코드만 실행합니다. 비동기 코드가 실행하는 동안, 호출자는 다음 코드 줄로 이동해서 실행하기 전에 해당 코드가 완료될때까지 기다립니다. 예를들어, 다음과 같이 갤러리에서 처음 3개의 사진을 가져오기 위해서, 3번의 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)
이 방법(approach)은 중요한 단점(drawback)이 하나 있습니다. 다운로드가 비동기로 진행되는 동안 다른 작업을 수행할수 있지만, downloadPhoto(named:) 호출은 한번에 하나만 실행합니다. 다음 하나의 다운로드를 시작하기 전에 각 사진 다운로드가 완료됩니다. 하지만 이러한 작업들을 기다릴 필요가 없습니다 - 각 사진은 독립적으로 또는 동시에 다운로드 할 수 있습니다.
비동기 함수를 호출하고 코드를 병렬로 실행하기 위해 상수(constant)를 정의할때 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:)를 3번 호출하는 것 모두 이전 하나가 완료되기를 기다리는 것 없이 시작합니다. 사용가능한 시스템 리소스가 충분하다면, 동시에 실행할 수 있습니다. 코드가 함수의 결과를 기다리기 위해 보류하지 않기 때문에, 이러한 함수 호출은 await로 표시되지 않습니다. 대신, photos가 정의된 줄까지 실행이 계속됩니다 - 이 시점에 프로그램은 비동기 호출의 결과가 필요하므로, 3개의 사진이 모두 다운로드 될때까지 실행을 일시정지하기 위해 await를 작성합니다.
다음은 이러한 2가지 방법에 대한 차이점을 생각하는 방법입니다.
- 다음 줄에 있는 코드가 함수의 결과에 의존될때 await로 비동기 함수를 호출합니다. 이는 순차적(sequentially)으로 실행되는 작업을 만듭니다.
- 코드에서 결과가 필요하지 않을때 async-let로 비동기 함수를 호출합니다. 이는 병렬로(parallel)로 실행되는 작업을 만듭니다.
- await와 await-let 모두 보류되어 있는 동안 다른 코드가 실행하는 것을 허용합니다.
- 두 경우 모두, 비동기 함수가 반환할때까지 필요한 경우에 실행을 일시정지되는 것을 나타내기 위해 보류가능한 시점에 await로 표시합니다.
또한, 동일한 코드에서 2가지 방법 모두를 혼합할수 있습니다.
작업과 작업 그룹(Tasks and Task Groups)
하나의 작업(task) 은 프로그램의 일부처럼 비동기적으로 실행할 수 있는 작업의 단위입니다. 모든 비동기 코드는 일부 작업(task)의 일부분으로 실행됩니다. 이전 섹션에서 설명한 async-le 문법은 하위작업(chilld task)을 만듭니다. 또한, 작업 그룹(task group)을 만들고 그 그룹에 하위작업(child tasks)를 추가할수 있습니다. 즉, 우선순위(priority)와 취소(cancellation)을 더 잘 제어할수 있고, 작업(task) 수를 동적으로 생성할 수 있습니다.
작업(tasks)은 계층구조로 정렬되어 있습니다. 작업 그룹(task group)내의 각 작업(task)은 같은 상위작업(parent task)을 가지고, 각 작업은 하위작업(child task)을 가질 수 있습니다. 작업과 작업 그룹간의 명시적인(explicit) 관계 때문에, 이러한 방법은 구조회된 동시성(structured concurrency)이라고 합니다. 비록 사용자가 정확성에 대한 약간의 책임을 지더라도, 작업간의 명시적인 상위-하위 관계는 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) }
}
}
작업 그룹에 대한 더 자세한 정보는 작업 그룹(TaskGroup)을 보세요.
구조화되지 않은 동시성(Unstructured Concurrency)
이전 섹션에서 설명한 동시성에 대한 구조화된 접근법 외에도 Swift는 구조화되지 않은 동시성(unstructured concurrency)을 지원합니다. 작업 그룹에 속한 작업과는 다르게 구조화되지 않은 작업(unstructured task)은 상위 작업(parent task)이 없습니다. 프로그램에서 필요한 모든 방법으로 구조화되지 않은 작업(unstructured task)을 완전히 유연하게 관리 할 수 있지만, 이러한 정확성에 대한 잭임은 전부 여러분에게 있습니다. 현재 actor에서 실행되지 않는 구조화되지 않은 작업(unstructured task)을 만들기 위해, Task.init(priority:operation:) 초기화를 호출합니다. 현재 actor의 일부가 아닌 구조화되지 않은 작업(unstructured task)을 만들기 위해 분리된 작업(detached task)이라고 하는 좀 더 특별한 Task.detached(priority:operation:) 클래스 메소드를 호출합니다. 이러한 두 작업 모두 작업과 상호작용하는 작업 핸들러(task handle)를 반환합니다. - 예를들어, 결과를 기다리거나 취소합니다.
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) 취소 모델을 사용합니다. 각 작업은 실행중에 적절한 시점에 취소(canceled)되었는지 확인하고, 적절한 방법으로 취소에 응답합니다. 하는 일에 따라 일반적으로 다음에 오는 것중 하나를 의미합니다.
- CancellationError와 같은 오류 던지기
- nil 또는 빈 콜렉션(collection)을 반환하기
- 부분적으로 완료된 작업 반환하기
취소를 확인하기 위해, Task.checkCancellation()을 호출하며, 작업이 취소된 경우에 CancellationError을 던지거나 Task.isCancelled의 값을 확인해서 코드에서 취소를 처리 합니다. 예를들어, 갤러리에서 사진을 다운로드 하는 작업에서 부분적인 다운로드를 삭제하고 네트워크 연결을 끊어야 할 수도 있습니다.
수동으로 취소하려면, Task.cancel()을 호출하세요.
Actors
클래스 처럼, actors는 참조 타입이므로, 클래스는 참조 타입(Classes Are Reference Types)에서 값 타입과 참조 타입의 비교는 클래스 뿐만 아니라 actors도 마찬가지로 적용됩니다. 클래스와 다르게, actors는 한번에 하나의 작업만 변경가능한 상태를 사용하도록 허용하며, 여러 작업들이 있는 코드에서 actors의 통일한 인스턴스로 상호작용하는 것을 안전하게 해줍니다. 예를들어, 다음은 온도를 기록하는 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를 시작하고, 한쌍의 중괄호(a pair of braces)로 정의합니다. TemperatureLogger actor는 actor 외부에서 사용할수 있는 프로퍼티들을 가지고 있고, actior의 내부에서만 최대값을 업데이트 하도록 제한하는 max프로퍼티가 있습니다.
구조체와 클래스와 동일한 초기화 구문을 사용해서 actor의 인스턴스를 생성합니다. actor의 프로퍼티와 메소드를 사용할때, 잠재적으로 보류할 시점을 표시하는데 await를 사용합니다. - 예를들어
let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
print(await logger.max)
// Prints "25"
예제에서, logger.max를 사용하는 것은 보류 시점이 가능합니다. actor는 한번에 하나의 작업만 변경 가능한 상태를 사용하도록 허용하기 때문에, 다른 작업의 코드가 이미 logger와 상호작용하고 있다면, 이 코드는 해당 프로퍼티를 사용하기 위해 기다리는 동안에 보류됩니다.
반대로(In contrast), actor의 부분의 코드는 actio의 프로퍼티를 사용할때 await를 작성하지 않습니다. 예를들어, 다음은 새로운 온도로 업데이트 하는 TemperatureLogger 메소드 입니다.
extension TemperatureLogger {
func update(with measurement: Int) {
measurements.append(measurement)
if measurement > max {
max = measurement
}
}
}
update(with:) 메소드는 이미 actor에서 실행하고 있으므로, max와 같은 프로퍼티를 사용할때 await를 표시하지 않습니다. 또한, 이 메소드는 왜 actor가 변경가능한 상태와 상호작용하기 위해 한번에 하나의 작업만을 허용하는지를 보여줍니다. actior의 상태에 대한 일부 업데이트는 임시로 불변성(invariants)을 깨트립니다. TemperatureLogger actor는 온도의 목록과 최대 온도를 추적하고 새로운 측정 값을 기록할때 최대 온도를 업데이트 합니다. 업데이트 하는 도중에 새로운 측정값을 추가하고. max 를 업데이트 하기 전에, 온도 기록기(temperature logger)는 임시로 일치하지 않는(inconsistent) 상태에 있습니다. 여러 작업이 동일한 인스턴스와 동시에 상호작용하는 것을 막으면 다음과 같은 이벤트 시퀀스와 같은 문제를 방지할 수 있습니다.
- update(with:) 메소드를 호출합니다. measurements 배열의 첫번째를 업데이트합니다.
- max를 업데이트하기 전에, 다른 곳에서 최대 값과 온도 배열을 읽을 수 있습니다.
- max값을 변경해서 업데이트를 마칩니다.
이 경우에, 다른 곳에서 실행중인 코드는 데이터가 임시로 유효하지 않는 동안에 update(with;)를 호출하는 중간에 actor에 대해 사용하는 것이 끼워져서(interleaved) 잘못된 정보를 읽게 됩니다. Swift actors를 사용할때 한번에 하나의 상태에서만 작업을 수행할 수 있기 때문에 이러한 문제를 막을 수 있고, 해당 코드는 보류 시점을 await로 표시한 곳에서만 중단될 수 있습니다. update(with:)는 보류 지점이 없으므로 업데이트 중에 데이터를 사용할 수 있는 다른 코드가 없습니다.
클래스의 인스턴스와 같이 actor의 외부에서 이러한 프로퍼티를 사용하려고 하는 경우에, 다음과 같이 컴파일시 오류가 발생합니다; 예를들어
print(logger.max) // Error
actor의 프로퍼티는 actor의 독립적인 로컬(local) 상태의 일부이기 때문에, await를 작성하지 않고 logger.max를 사용하는 것은 실패합니다. Swift는 actor의 코드 내부에서만 actor의 로컬(local) 상태를 사용할수 있는 것을 보장합니다. 이러한 보장(guarantee)을 actor 격리(actor isolation)라고 합니다.
전송가능한 타입(Sendable Types)
Task와 actiors는 프로그램을 동시에 안전하게 실행할 수 있는 조각으로 나눌수 있습니다. task 내부 또는 actor의 인스턴스 내부에서, 변수나 프로퍼티와 같이 변경가능한 상태를 포함하는 프로그램의 부분을 동시성 도메인(concurrency domain)이라고 합니다. 데이터의 종류중 일부는 변경가능한 상태를 포함하기 하지만, 중복 접근으로부터 보호하지 않기때문에, 동시성 도메인 간에 공유할 수 없습니다.
타입은 하나의 동시성에서 다른 도메인으로 공유할 수 있튼 타입을 전송가능한(sendable) 타입이라고 합니다. 예를들어, actor 메소드를 호출할때 인자(argument)로 전달되거나 task의 결과로 반환될 수 있습니다. 이 장의 앞부분에 있는 예제는 동시성 도메인간에 전달되는 데이터에 대해서 항상 안전하게 공유할 수 있는 간단한 값 타입을 사용하기 때문에, 전송 가능성에 대해서 논의하지 않았습니다. 반면, 일부 타입은 동시성 도메인간에 전달되는것이 안전하지 않습니다. 예를들어, 변경가능한 프로퍼티를 포함하고 해당 프로퍼티에 대한 접근(access)을 직렬화(serialize)하지 않는 클래스는 다른 task간에 해당 클래스의 인스턴스를 전달할때 예측할 수 없고 잘못된 결과를 초래할 수 있습니다.
Sendable 프로토콜을 준수하도록 선언해서 전송가능한 타입임을 표시합니다. 해당 프로토콜은 요구되는 코드가 없지만, Swift에서 강요하는 의미론적인 요구사항이 있습니다. 일반적으로, 타입을 전송할 수 있는 세가지 방법이 있습니다.
- 해당 타입이 값 타입이고, 변경가능한(mutable) 상태는 다른 전송가능한 데이터를 만듭니다 - 예를들어, 전송가능한 저장 프로퍼티(stored properties)가 있는 구조체 또는 연관된(associated) 값을 전송할수 있는 열거형 입니다.
- 해당 타입은 변경가능한(mutable) 상태가 아니고, 불변(immutable) 상태는 다른 전송가능한 데이터를 만듭니다 - 예를들어, 읽기 전용 프로퍼티를 가진 구조체나 클래스.
- 해당 타입은 @MainActor로 표시된 클래스나 특정 스레드나 큐의 프로퍼티에 대한 접근(access)을 직렬화하는 클래스 처럼, 변경가능한(mutalbe) 상태의 안전을 보장하는 코드 입니다.
의미론적인 요구사항에 대한 자세한 내용은, 전송가능한(Sendable) 프로토콜을 참조하세요.
전송가능한 프로퍼티만 있는 구조체와 연관된 값만 전송가능한 열거형 처럼, 몇몇 타입은 항상 전송가능(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으로 표시되지 않기 때문에 암묵적(implicitly)으로 전송가능합니다.
struct TemperatureReading {
var measurement: Int
}
'Swift > Language Guide' 카테고리의 다른 글
흐름제어(Control Flow) (0) | 2022.06.21 |
---|---|
기초(The Basics) (0) | 2022.06.21 |
불분명한 타입(Opaque Types) (0) | 2019.12.12 |
컬렉션 타입(Collection Types) (0) | 2019.03.05 |
문자열과 문자(Strings and Characters) (0) | 2019.02.28 |
기본 연산자(Basic Operators) (0) | 2019.02.26 |
Advanced Operators (0) | 2018.09.18 |
Access Control (0) | 2018.09.18 |