Swift: Sendable

틀틀보·2025년 5월 8일

Swift

목록 보기
4/19

동시성 환경에서 data-race를 방지하고 여러 Thread간에 안전하게 값을 전달하기 위한 프로토콜

Sendable을 준수하는 타입은 여러 Thread에서 동시에 접근하더라도 값을 안전하게 전달할 수 있음을 나타냄.

Sendable을 준수하는 값 타입

struct와 enum

값 타입은 인스턴스를 복사하여 전달되기 때문에 동시성 환경에서 기본적으로 안전하게 사용할 수 있음.

  • 모든 저장 프로퍼티는 Sendable 해야함.
  • Sendable을 준수하지 않는 참조 타입을 포함하지 않아야 함.
struct Point {
    var x: Int // Int는 기본적으로 Sendable을 준수함!
    var y: Int
    let data = TestData() // 명시적으로 Sendable을 준수하지 않기에 컴파일러가 경고
}

class TestData {
	var isData: Bool = false
}

Sendable 프로토콜을 선언하지 않은 TestData 객체를 가진 Point 구조체는 컴파일러가 경고.

Sendable을 준수하는 참조 타입

Sendable을 기본적으로 준수하는 actor

  • actorSendable을 암시적으로 준수
  • actor 내부의 상태는 actor의 메서드만 접근 가능하기에 이미 data-race 방지

⚠️ 주의 사항

  • actor 간에 데이터를 전달 및 반환할 경우, 해당 데이터는 Sendable 해야 함.
  • 다만 actor 내부에 Sendable하지 않은 타입을 가지고 있는 것은 가능. actor 자체가 프로퍼티에 대한 접근을 직렬화하기 때문.
class NonSendableClass {
    var value: Int = 0
}

actor FirstActor {
    let data = NonSendableClass()
    
    func sendData(to actor: SecondActor) async {
        await actor.receive(data: data) // 컴파일러 에러 발생
    }
}

actor SecondActor {
    func receive(data: NonSendableClass) {
        // ...
    }
}

class의 경우

  • final로 선언
    • 상속을 통한 불변성 깨짐 방지
  • 모든 저장 프로퍼티는 불변(let) 하거나 Sendable 할 것
  • 슈퍼클래스가 없거나 NSObject를 상속할 것
final class SendableClass: Sendable {
	private var property1: Int // private이더라도 내부 메서드가 property1을 수정하면?
    let property2: Int
    ...
}
  • private 접근자를 쓰더라도 내부 메서드가 해당 프로퍼티에 수정을 일으키면 data-race 발생 가능

@unchecked Sendable

컴파일러가 Sendable 검사를 생략

타입이 실제로는 Thread Safe하나 컴파일러가 잡아낼 경우, 검사를 생략하도록 하는 것

final class MyClass: @unchecked Sendable {
    var name: String // 에러를 발생시키지 않음!
}

@Sendable 클로저

해당 클로저가 동시성 환경에서 안전하게 실행될 수 있음을 컴파일러에게 알리는 속성

규칙

  • 캡처된 값은 모두 Sendable을 준수할 것
  • 클로저는 외부 변수를 값 타입으로 캡처
  • 파라미터와 반환 타입은 Sendable일 필요 X
값 타입 캡처
var count = 0

let increment: @Sendable () -> Void = { [count] in
    print("Captured count: \(count)")
    // count를 값으로 캡처하므로 동시성 환경에서도 안전하게 사용 가능
}
참조 타입 캡처 (오류)
class User {
    var name = "Alice"
}

var user = User()

let greet: @Sendable () -> Void = { [user] in
    print("Hello, \(user.name)")
    // 'user'는 참조 타입이므로 동시성 환경에서 안전하지 않음
 }
  • 캡처 리스트를 사용하더라도 참조(주소)를 복사하는 것이지, 객체 자체를 복사 X
  • 컴파일러가 경고

✨ 해결책

  • 딥 카피(Deep Copy): 실제 객체를 복사해서 캡처
  • 동기화 메커니즘 사용: 객체 내부적으로 스레드 안정성 확보
  • @unchecked Sendable 사용

추가: MainActor

@MainActor 키워드로 정의된 함수는 암시적으로 Sendable로 간주 X, 명시해줘야 함.
MainActor가 Sendable 컴파일러 오류

https://www.hackingwithswift.com/quick-start/concurrency/sending-data-safely-across-actor-boundaries

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

https://www.youtube.com/watch?v=Ka28hay60VQ

profile
안녕하세요! iOS 개발자입니다!

0개의 댓글