ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Swift] Sendable 이해하기
    Swift 2025. 2. 11. 20:29
    반응형

     
    Sendable은 data races의 위험 없는 thread-safe 한 타입임을 보장하는 protocol 입니다.
    Sendable을 따른다는 건, 동시성 걱정 없이 전달할 수 있다는 걸 의미합니다.
    따라서 actor의 메서드 파라미터로도 전달할 수 있습니다.

    Swift6 부터는 컴파일 단계에서 동시성이 필요한 경우 Sendable을 체크해서 검증한다고 합니다.
    예를 들어, Task를 만들 때 공유 데이터가 Sendable을 준수하지 않으면 컴파일 에러를 발생시킵니다.

     

    아래 4가지는 안전하게 파라미터로 전달할 수 있습니다.
    즉, Sendable 합니다.

    1. 값 타입 (Value types)

    struct나 enum은 내부 변수까지 모두 Sendable을 준수하는 값들로만 구성되어 있다면 Sendable 을 채택할 수 있습니다.
    단, 아래 케이스에선 Sendable 선언 없이 자동으로 Sendable을 준수합니다.

    1.1. @frozen

    저도 이번 기회에 공부하게 되었는데요.
    @frozen은 struct나 enum이 변경되지 않을 것임을 보장하는 선언이라고 합니다.
    더 이상 멤버가 추가되지 않을 것임을 확정적으로 선언하는 거라고 해요.
    즉, 더 이상 멤버나 case 추가가 불가능해집니다. class가 더이상 상속되지 않을 거라고 final을 붙이는 것과 비슷한 느낌 같습니다.

    @frozen struct UserInfo {
        let name: String
        let age: Int
    }

     

    1.2 internal or private 한 value types

    그렇다고 하네요. public 이라면 명시적으로 Sendable을 붙여줘야 함!

    public struct PublicStruct: Sendable {
        let id: Int
    }

     

    1.3 @unchecked Sendable

    저는 웬만하면 안 쓰고 싶지만 강제로 체크하는 방법도 있습니다.

    final class Logger {} // 기본적으로 Sendable이 아님
    
    struct LogMessage: @unchecked Sendable {
        let message: String
        let logger: Logger  // Sendable이 아닌 타입을 포함하지만 강제로 Sendable 선언
    }

     

    2. Reference types with no mutable storage

    immutable한 레퍼런스 타입은 Sendable을 보장합니다.
    final + let 이라면 Sendable을 준수합니다. 정확히 let만 가능한 건 아니지만 그런 느낌으로 적었습니다. (불변 프로퍼티라는 의미로 사용)
    아래 2가지 조건을 만족한다면 Sendable을 준수합니다.
    1. final class
    2. 모든 프로퍼티가 불변
    final을 명시해야 하는 이유는 상속해서 mutable한 값이 추가될 가능성도 있기 때문입니다.
     

    3. Reference types that internally manage access to their state

    참조타입이더라도 내부에서 안전하게 상태가 관리되고 있다면 Sendable을 준수합니다.
     

    3.1 Sendable Actor

    이를 준수하는 대표적인 예시가 actor 입니다. actor라면 Sendable을 준수합니다.
    - actor 참고

    3.2 Sendable Classes

    class가 Sendable을 준수하려면 아래 조건들을 만족해야 합니다.
    - final class
    - 모든 프로퍼티가 immutable 하고 Sendable을 준수해야 함.
    - super class가 없거나 Super class가 NSObject인 경우 Sendable 이어야 함.
     

    4. Functions and closures (by marking thme with @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 클로저는 value type만 참조가 가능합니다.
    아래와 같이 reference type인 class를 직접 참조하면 문제가 발생합니다.

    class Counter {
        var value: Int = 0
    }
    
    let counter = Counter()
    
    let unsafeClosure: @Sendable () -> Void = {  // ❌ 오류 발생!
        counter.value += 1  // 🚨 'Counter'는 Sendable이 아님
    }

     
    위와 같은 경우 Counter를 actor 로 변경하던가 Counter 자체가 아니라 필요한 값을 참조해야 합니다.

    let counterValue = counter.value  // ✅ 값 복사
    
    let safeClosure: @Sendable () -> Void = {
        let newValue = counterValue + 1  // ✅ 값 복사이므로 안전
        print("새로운 값: \(newValue)")
    }

     
    참고로 Counter가 Sendable 을 준수한다고 해도 클로저에서 직접 참조할 수 없습니다.
    Sendable은 "값을 안전하게 주고받을 수 있다" 는 의미이지 안전하게 공유될 수 있다는 의미는 아닙니다.
     

    5. 기타

    Sendable Tuple

    Tuple의 경우 모든 요소가 Sendable을 준수하다면 Tuple도 Sendable을 준수합니다.

    let sendableTuple: (Int, String) = (42, "Hello")  // ✅ 자동으로 Sendable을 준수함

     

    Sendable Metatypes

    Int.Type 과 같은 메타 타입은 Sendable을 준수합니다.
    (GPT는 Sendable을 한 클래스의 메타 타입이라면 Sendable을 준수한다는데 정확한 내용은 찾지 못했습니다.)
     
    감사합니다.
     
    # Ref
    https://developer.apple.com/documentation/swift/sendable#Sendable-Structures-and-Enumerations

     

    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

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

     

    Documentation

    docs.swift.org

     
     
     

    반응형

    댓글

Designed by Tistory.