Sendable에 대해서 알아보자

https://developer.apple.com/documentation/swift/sendable/

 

Sendable | Apple Developer Documentation

A thread-safe type whose values can be shared across arbitrary concurrent contexts without introducing a risk of data races.

developer.apple.com

 

Sendable

Sendable은 Swift Concurrency 환경에서 여러 스레드나 액터 간에 안전하게 값을 전달할 수 있음을 보장하기 위한 프로토콜이다. 이는 데이터 경합(data race)과 관련된 문제를 방지하고, 동시성 안전성을 확보하는데 중요한 역할을 한다.

 

Sendable은 타입이 다른 스레드나 액터로 안전하게 전달될 수 있음을 컨파일러에 알려주는 마커 프로토콜이다.

 

이를 통해 값이 한 스레드에서 수정되다가 다른 스레드에서 읽힐때 발생할 수 있는 데이터 경합 문제를 방지할 수 있다.

 

일반적으로, Sendable로 채택된 타입은 내부 상태가 불변이거나 변경이 안전하게 이루어지도록 설계되어있어야 한다.

 

예를 들어 Swift의 기본 값 타입(Int, String, Array 등)은 값 타입 이므로 기본적으로 Sendable로 간주된다.

 

Swift Concurrency와 Sendable

구조적 동시성(Structured Concurrency) :

Swift Concurrency에서는 여러 작업(Task)과 액터 간의 값 전달이 빈번하게 발생한다. Sendable은 이 값이 안전하게 전송되도록 보장한다.

 

컴파일러의 역할 :

컴파일러는 Sendable 프로토콜에 맞지 않는 타입이 동시성 경계를 넘어서 전달되는 경우, 경고나 에러를 발생시킨다. 이를 통해 개발자는 잠재적인 동시성 버그를 미연에 방지할 수 있다.

 

액터 메서드나 비동기 함수의 매개변수, 반환값 등에 전달되는 값은 Sendable이어야 한다. 만약 Sendable이 아닌 타입이 사용 된다면, 컴파일러는 안전하지 않은 동시성 데이터 접근을 감지하고 이를 알려준다.

 

Sendable 채택 방법

대부분의 값 타입(구조체, 열거형 등)은 모든 저장 프로퍼티가 Sendable이면 자동으로 Sendable로 간주된다. 만약 직접 만든 타입이 동시성 안전성을 보장한다면, 해당 타입에 Sendable을 채택한다고 명시할 수 있다.

struct UserData: Sensdable {
	let id: Int
	let name: String
}

클래스는 참조 타입이므로, 단순히 Sendable을 채택하는 것만으로 충분하지 않다. 클래스의 내부 상태가 동시성에 안전하도록 설계되어야 하고 보통 액터를 통해 관리하는 것이 일반적이다.

 

Sendable과 관련된 주의사항

클로저 캡쳐 :

비동기 함수나 액터 간에 클로저가 캡쳐하는 값들도 Sendable해야 한다. 그렇지 않으면 컴파일러가 경고를 발생시키기 때문

 

타입 안전성 :

Sendable을 채택한 타입이 정말로 동시성 안전한지 확인해야한다. 단순히 프로토콜을 채택하는 것만으로는 내부 동작까지 보장되지 않으며, 불변성을 유지하거나 동기화 매커니즘을 사용하는 등의 추가 노력이 필요할 수 있다.

 

전환 및 상속 :

만약 기존에 작성한 타입이 Sendable 요구사항을 충족하지 않는다면, 동시성 환경으로 전환할 때 해당 타입을 수정하거나, 필요한 경우 별도의 동기화 코드를 추가해야한다.


비동기 작업이나 동시성 경계를 넘어서 실행되는 클로저의 경우, 캡처하는 값들이 Sendable해야한다. 이때 클로저에 @Sendable 어노테이션을 붙여 사용하게 된다

func processData(value: Int, completion: @Sendable (Int) -> Void) {
    Task {
        // 비동기 작업 수행
        let result = value * 2
        completion(result)
    }
}

이 예제에서 completion 클로저는 @Sendable로 선언되어 클로저 내부에서 캡처되는 모든 값들이 동시성 안전하다는 것을 보장한다!

다음은 복합 타입 내부에 Sendable 요소가 포함되어있는 형태를 봐보자!

 

구조체를 만들때 그 내부에 배열이나 다른 구조체가 포함된다면, 이 모든 구성 요소들이 Sendable 이어야 한다.

struct User: Sendable {
    let id: Int
    let name: String
}

struct ChatRoom: Sendable {
    // 배열도 구조체 배열인 경우, 배열의 요소(User)들이 Sendable이면 자동으로 Sendable로 간주됨
    let participants: [User]
    let roomName: String
}

이렇게 구성된 ChatRoom 타입은 동시성 경계를 넘어 안전하게 전달할 수 있다.

 

이렇게 Sendable의 개념에 대해서 알아보았다 아직 완벽하게 이해하려면 조금 더 사용해보고 다시 공부해야할 것 같다 :0 공부는 해도 해도 끝이없네~