Swift Language Mode가 버전 업이 되면서 동시성 환경에서 데이터 경합(Data Race)이 발생할 여지가 있는 경우 Swift 5에서는 경고로 끝났지만 Swift 6에서는 Compile 단계에서 이것을 사전에 방지하도록 강제하고 있다. 이번 포스트에서는 동시성 환경에서 데이터 경합을 방지할 수 있는 두번째 개념 Sendable
protocol에 대해 알아본다.
공식 문서에 등장하는 Sendable
protocol의 개념은 다음과 같다.
Sendable
임의의 동시성 context에서 데이터 경합 없이 thread-safe하게 값을 전달할 수 있는 타입
하나의 동시성 도메인에서 다른 동시성 도메인으로 값을 전달할 때 Sendable
타입을 전달할 수 있다. 예를 들면 actor의 메서드의 인자로 Sendable
타입을 전달할 수 있다.
일반적으로 Sendable
protocol을 가장 흔히 볼 수 있는 곳이 Task
의 제네릭 파라미터 Success
에서 확인할 수 있다. Task
는 새로운 비동기 작업을 생성하여 동시적으로 실행될 수 있으므로 Task
로 전달되는 타입은 Sendable
protocol을 채택하여 데이터 경합을 방지할 수 있어야한다.
struct Task<Success: Sendable, Failure: Error> {
var value: Success {
get async throws { … }
}
}
따라서 아래와 같이 Task
에 전달되는 타입이 Chicken
class와 같이 Sendable
protocol을 채택하고 있지 않는 경우 컴파일 에러가 발생하게 된다.
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
)
Sendable
protocol은 자체적인 프로퍼티나 메서드 구현 요구사항이 있진 않지만 컴파일 레벨에서 지켜야할 의미적 요구 사항이 존재하며 그 내용은 다음과 같다.
struct
타입과 enum
타입에서 Sendable
protocol을 채택하기 위해서는 구조체와 열거형의 멤버와 associated value들은 모두 Sendable
protocol을 채택해야 한다.
또한 아래의 요구사항을 충족하는 경우 암시적으로 Sendable
을 채택한다.
public
으로 선언 되지 않거나 @usableFromInline
로 표시되지 않은 경우이전 포스트에서 다루었던 actor 타입 또한 암묵적으로 Sendable
protocol을 채택한다.
class
타입에서 Sendable
protocol을 채택하기 위해서는 다음과 같은 요구사항을 필요로 한다.
final
클래스Sendable
protocol을 채택한 경우NSObject
를 부모 클래스로 가지는 경우또한 클래스가 @MainActor
로 선언되어 있는 경우 암묵적으로 Sendable
protocol을 따른다.
@MainActor
로 선언되어 있는 경우 메인 스레드에서 동작을 보장하기 때문에 데이터 경합이 일어나지 않는다.
함수는 기본적으로 protocol을 채택하는 것이 불가능하지만 예외적으로 @Sendable
속성을 사용하여 함수와 클로저가 Sendable
을 채택하게 할 수 있다. 클로저의 경우 클로저가 캡쳐하는 값은 모두 Sendable
을 채택해야 한다. 또한 Sendable
클로저가 값을 캡쳐할 때, 값 복사에 의해 캡쳐한다.
let sendableClosure = { @Sendable (number: Int) -> String in
if number > 12 {
return "More than a dozen."
} else {
return "Less than a dozen"
}
}
튜플 내부 요소가 모두 Sendable
protocol을 채택한다면, 암묵적으로 해당 튜플은 Sendable
protocol을 채택한다.
Int.Type
과 같이 메타 타입의 경우 암묵적으로 Sendable
protocol을 채택한다.