본문 바로가기
Swift/오류 개발자

Swift | Converting non-sendable function value to '@Sendable () -> ()' may introduce data races 오류 이해하기

by UDDT 2025. 4. 5.

 주의

    이 오류는 Swift5에서는 뜨지 않고, Swift6에서 뜬다.

    (서치해보니, @Sendable 이라는 개념이 Swift 5.5부터 나온 것 같다)

    아마 Swift 5에서는 이 오류가 경고정도로만 나오거나, 아예 나오지 않을 수 있다.

 어쩌다가 이 오류를 만나게 되었나?

    클로저를 공부하다가, 클로저의 특성이 일급시민 객체의 특성을 따른다는 것을 공부하게 되었다.

  String Type을 가진 변수에 다른 String를 대입할 수 있는 것처럼, 함수에도 클로저를 대입하고자 했다.

 

// string이 Type이기 때문에 변수에 담을 수 있고 동일한 타입의 값으로 변경이 가능하다
var stringNumber = "123"
stringNumber = "456"

// 마찬가지로 int도 Type이기 때문에 변수에 담을 수 있고 동일한 타입의 값으로 변경이 가능하다
var number = 123
number = 456

// 함수도 () -> () Type이므로 담을 수 있고,
var myFunction = printMyName

func printMyName() {
    print("당신의 이름은 홍길동입니다.")
}

myFunction()

// closure도 () -> () Type이기 때문에 담을 수 있음
var yourFunction = {
    print("당신의 이름은 고길동입니다")
}

// 같은 Type이니까 담아주려고 했는데, 에러가 발생했다.
myFunction = yourFunction // 에러 발생

 

   myFunction = yourFunction을 했더니 하단의 에러가 발생했다.

Converting non-sendable function value to '@Sendable () -> ()' may introduce data races

 

   아니 분명히 myFunction도 () -> () Type이고, yourFunction도 () -> () Type인데 왜 이러는걸까?

아니 왜 나한테만 그러는데~

 

Swift의 Type 추론

    에러 메시지를 다시 한번 보자

Converting non-sendable function value to '@Sendable () -> ()' may introduce data races

 

    전송이 불가능한 함수 값을 '@Sendable () -> ()'로 변환하면 데이터의 꼬임(data race)이 일어날 수 있습니다.

   엥? 둘다 () -> () Type 아니었나?

   @Sendable은 뭐고 갑자기 어디서 튀어나왔을까?

 

Sendable 쉽게 이해하기

    위 오류는 비동기 환경(Swift concurrency)에서 스레드 안전성(thread safety) 문제를 막기 위해 Swift가 알려주는 경고문이다.

(아마 더욱 안전한 환경을 위해 Swift가 새롭게 도입한 시스템 같음)

Swift에서는 @Sendable이라는 특성을 통해, 클로저가 스레드간 안전하게 전달될 수 있는지를 검사하는데,

비동기 작업이나 Task 안에서 사용하는 클로저는 Sendable이어야 함.

 

   클로저는 기본적으로 non-Sendable인데, 

var count = 0

let closure = {
    count += 1  // 주변 변수 캡처!
}

 

   위의 코드에서처럼 closure는 변수(mutable한 값)도 캡처할 수 있음

  따라서, 값이 변할 가능성이 있기 때문에 기본적으로는 non-Sendable임.

 

    위에서 선언한 함수와 클로저 둘다 () -> () Type은 맞지만, 

var myFunction = printMyName

func printMyName() {
    print("당신의 이름은 홍길동입니다.")
}

 

    컴파일러가 타입을 추론할 때 myFunction의 타입을 @Sendable 함수로 추론함

var yourFunction = {
    print("당신의 이름은 고길동입니다")
}

myFunction = yourFunction

 

  후에 대입한 클로저가 일반 클로저(non-sendable)이라서 

"전송이 불가능한 함수 값을 '@Sendable () -> ()'로 변환하면 데이터의 충돌이 일어날 수 있습니다."라고 알려준 것이다.

 

    해결 방법으로는,

  1. 함수의 타입을 명확히 () -> Void라고 명시해주거나

// 타입을 명확히 () -> Void라고 명시해주는 방법
var myFunction: () -> Void = printMyName

  

  2. 반대로 클로저에 @Sendable을 붙여주는 방법이 있다.

     * 단 이때는 클로저 안에서 캡처하는 값들이 반드시 Sendable(변하지 않는 값)이어야 함

// 클로저를 @Sendable로 명시하는 방법
var yourFunction: @Sendable () -> Void = {
    print("당신의 이름은 고길동입니다")
}

 

 

Sendable 어렵게 이해하기(with WWDC 2022)

  https://developer.apple.com/videos/play/wwdc2022/110351/

 

Eliminate data races using Swift Concurrency - WWDC22 - Videos - Apple Developer

Join us as we explore one of the core concepts in Swift concurrency: isolation of tasks and actors. We'll take you through Swift's...

developer.apple.com

  ( 어떻게든 이해해보고 싶어서 WWDC를 보면서 Keynote로 그려가며 이해하고자 했으나...... 어느정도만 이해하고 실패... )

 

 

 

  이걸 이해하려면, 비동기 작업을 해보고나서 다시 들여다봐야 비로소 이해할 수 있을 것 같다....

 

 @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을 공식문서에서 찾아보면 다음과 같이 나와있다.

A thread-safe type whose values can be shared across arbitrary concurrent contexts without introducing a risk of data races.
("값이 어떤 동시 실행 환경에서도 안전하게 공유될 수 있도록 설계된, 스레드-안전한 타입이다. 이 타입은 데이터 레이스(꼬임)의 위험 없이 사용할 수 있다.")
즉, 이 말은 클로저가 스레드 간 안전하게 전달될 수 있는지를 확인하는 것이다.

 

   Sendable 타입의 값은 공유되는 mutable 상태의 값을 가지지 않거나 또는 해당 상태를 (lock)으로 보호하거나
특정 액터(actor)에서만 접근하도록 강제할 있어야 한.

 

   Sendable Type의 값은 하나의 동시성 영역에서 다른 영역으로 안전하게 전달할 수 있다.

예를 들어, 액터의 메서드를 호출할 때 Sendable 값을 인자로 넘겨줄 수 있다.

아래의 항목들은 모두 Sendable로 표시될 수 있다.

 

   - 값 타입(enum, struct 등)

   - 변경 가능한 저장 속성이 없는 참조 타입(let으로 선언된 class)

   - 내부적으로 상태 접근을 관리하는 참조 타입(lock을 쓰거나, actor 안에서만 상태를 변경하도록 만든 class)

   - 함수와 클로저(@Sendable로 표시한 경우에만)

 

   이 프로토콜은 요구하는 메서드나 프로퍼티는 없지만,

컴파일 시점에 검사되는 의미상의 규칙(semantic requirements) 존재한다.

 

   이러한 요구사항들은 아래 섹션에 나열되어 있다.

 Sendable 준수하려면, 선언은 반드시 해당 타입을 정의한 파일 안에서 해야 한.

 

   컴파일러의 검사 없이 Sendable을 선언하려면,

 @unchecked Sendable을 사용하면 된다. (단, 타입이 안전한지의 여부는 개발자 본인의 책임이다.)

 예를 들어, 락(Lock)이나 큐(Queue)를 사용해 상태 접근을 보호해야 한다.

 @unchecked Sendable을 사용하면 Sendable은 반드시 같은 파일에서 선언해야한다는 규칙이 적용되지 않는다.

최근댓글

최근글

skin by © 2024 ttuttak