Xcode에서 Swift Language Version을 Swift 6로 변경하거나 변경하기 전 Build Setting 옵션 중 Strict Concurrency Checking을 활성화하면 동시성 환경에서 Data Race가 발생할 가능성이 있는 곳에서 에러 또는 경고를 표시한다. 이번 포스트에서는 이를 통해 점진적으로 Swift 6로 마이그레이션 하면서 Xcode에서 발생하는 경고를 해결하는 방법을 정리 해보고자 한다.
아래에서 설명하는 방법들은 단순히 Xcode의 경고나 에러를 해결함과 동시에, 동시성 환경에서 발생할 수 있는 Data Race를 방지하는 방법들을 소개한다. 일부는 당장의 빌드를 위해 임시로 에러를 잠시 사라지게 하는 방법들도 포함하지만, 대부분의 해결 방법은 동시성 환경에서 공유 자원에 동시 접근을 막아 Data Race가 발생하는 상황을 사전에 방지한다. Swift의 동시성 환경에서 공유 자원에 동시 접근을 방지하는 기본적인 방법은 이전 포스트를 참고하기 바란다.
멀티 스레드 환경에서 전역 변수는 데이터 경합이 발생할 여지가 있다. 따라서 전역으로 선언된 변수는 Swift 6 Language Mode에서 에러를 발생 시키므로 아래의 방법을 통해 경고 또는 에러를 해결할 수 있다.
@MainActor
annotation을 추가하여 전역변수가 메인 스레드에서 동작함을 보장할 수 있도록 한다. nonisolated(unsafe)
키워드를 사용하여 Actor의 격리를 무시하도록 한다.위의 사진의 문서에 표시 되어 있는 것과 같이 특정 delegate 메서드는 main thread에서 동작을 보장한다. UI Framework 관련 delegate 메서드가 대부분 그러하다.
반대로 위의 사진의 문서처럼 임의의 스레드에서 동작하며 어떤 스레드에서 동작하는지 알 수 없는 delegate 메서드도 존재한다.
위의 사진과 같이 UI Layer에 속하는 Recaffeinater
와 같이 @MainActor
annotation으로 Main thread에서 동작하는 Actor에서 다른 thread에서 동작하는 Delegate Method를 구현하는 경우 Swift 6 환경에서 에러를 발생 시킨다. 이런 경우 메서드에 nonisolated
키워드를 사용하여 Actor의 격리에서 분리 시킬 수 있다.
또한 위의 사진과 같이 Actor의 격리에서 분리한 Delgate Method 내부에서 Actor에 의해 보호되는 프로퍼티에 접근하는 경우 또 다른 에러를 마주하게 된다. 이런 경우 Main Actor에서 Actor에 의해 보호되는 프로퍼티에 접근하도록 수정하면 에러를 해결할 수 있다.
만약 Delegate Method가 Main Actor에 의해 실행되는 것을 문서 등을 통해 알고 있는 경우, assumeIsolated
를 사용할 수 있는데, 이것은 새로운 Task를 생성하는 것이 아닌 단순히 Swift 컴파일러에게 해당 코드가 Main Actor에 의해 동작함을 알려주는 역할을 한다. 여기서 만약 Main Actor가 아닌 다른 Actor에 의해 동작한다면 프로그램은 강제 종료 된다.
위에서 언급한 방법 대신 기존 코드를 그대로 유지하고 단 하나의 키워드로 동시성 관련 에러를 한번에 없애는 방법도 존재한다. 위의 사진과 같이 @preconcurrency
annotation을 사용하는 방법인데, 이 경우 구현된 Delegate Method가 같은 격리된 Actor를 사용하는 것을 가정하고 그렇지 않은 경우 프로그램을 강제 종료 시킨다.
위의 방법 외에 actor 타입을 사용하거나 Sendable 프로토콜을 준수 하도록 하여 동시성 환경에서 Data Race를 방지한다면 Swift 6 Language mode에서 발생하는 경고 또는 에러를 해결할 수 있다.