기존 엄격한 동시성 검사와 처리에 관해 개발자의 피로감을 해소시키기 위해 개선된 동시성
기존 코드에서 Swift 6로 전환 시, 수많은 Sendable 경고 + Actor 격리 에러 발생으로 인한 처리 피로감 발생
예를 들어 UI 코드 일부에 @MainActor를 붙이면, 그와 연결된 모든 프로토콜, 콜백, 클로저에 연쇄적으로 @MainActor나 @Sendable을 붙여야 하는 도배 현상 발생
특정 상태에 격리되지 않은(nonisolated) async 함수를 호출하면 무조건 글로벌 스레드 풀로 컨텍스트가 전환
메인 스레드에서 즉시 처리해도 안전한 가벼운 작업조차 불필요한 스레드 점프를 일으켜 성능 저하와 예기치 않은 타이밍 이슈 발생
@MainActor
class UIViewController {
func updateUI() async {
// MainActor에서 시작
await loadData()
// 작업 완료 후 다시 MainActor로 돌아옴
}
}
// 명시적인 격리가 없는(nonisolated) async 함수
func loadData() async {
// 🚨 문제점: MainActor에서 호출했음에도 불구하고,
// 진입하자마자 무조건 Generic Executor(백그라운드 스레드)로 튕겨 나감.
// 가벼운 작업이어도 불필요한 스레드 전환 비용 발생
await Task.sleep(nanoseconds: 1_000_000_000)
}
코드는 기본적으로 단일 스레드에서 실행된다고 가정
동시 접근이 없는 순차 실행 모델을 기본 (하나의 Actor에서 순차 처리)
-default-isolation MainActor 컴파일러 옵션을 적용하면 작성된 코드들이 기본적으로 @MainActor에서 격리 처리될 수 있도록 추가
코드는 @MainActor에서 순차적으로 처리되도록 하되, 필요에 따라 비동기, 병렬성 코드를 점진적으로 추가할 수 있도록 개선
기존에 돌아가던 코드들이 돌아갈 수 있도록 하고 점진적으로 개선할 수 있게 됨.
이전에는 nonisolated async 메서드를 호출하면 무조건 글로벌 스레드 풀로 컨텍스트 전환 발생
이로 인해 불필요한 스레드 점프가 발생하고 클래스 타입에서 에러가 다수 발생
✅ Swift 6.2에서는 기본적으로 호출자의 실행 컨텍스트를 그대로 유지
즉, 메인 액터에서 호출했다면 불필요하게 백그라운드로 나가지 않고 메인 스레드 내에서 안전하게 비동기 대기를 수행
async 함수는 다른 executor로 이동하지 않고 호출자의 실행 context를 유지하도록 동작
⚠️ 단, 필요에 따라 다른 executor로 이동하기도 함.
@MainActor
class UIViewController {
func updateUI() async {
// MainActor에서 시작
await loadData()
// 여전히 MainActor
}
}
// Swift 6.2: 호출자의 컨텍스트를 유지하는 async 함수
func loadData() async {
// ✅ MainActor에서 호출되었으므로, 스레드 이동 없이 MainActor 환경을 유지함.
await Task.sleep(nanoseconds: 1_000_000_000)
// ✅ Suspension(일시 중단) 이후 재개될 때도 호출자의 actor context를 그대로 유지함.
}
@concurrent 속성을 통해 명시적으로 병렬 실행 허용을 명시
항상 다른 스레드에서 실행되는 건 아님.
기본적으로 호출자 컨텍스트를 유지하는 방향으로 바뀌었기 때문에, 무거운 작업을 실제로 병렬 스레드 풀로 보내고 싶을 때 사용하는 새로운 속성
개발자의 직렬, 병렬 실행에 관한 의도를 명확하게 표시
Swift의 핵심 가치 Progressive Disclosure
처음부터 전부 알 필요 없이 필요한 만큼, 단계적으로 배워나갈 수 있도록 개선
// 1단계: 일반적인 동기 함수 (기본적으로 MainActor 등 단일 스레드에서 안전하게 실행)
func process(data: Data) -> String {
return String(decoding: data, as: UTF8.self)
}
// 2단계: 비동기 대기가 필요한 경우 async 추가
// (호출자의 컨텍스트를 유지하므로, 여전히 스레드 전환 에러 걱정 없이 안전함)
func process(data: Data) async throws -> String {
try await Task.sleep(for: .seconds(1)) // API 호출 대기 등
return String(decoding: data, as: UTF8.self)
}
// 3단계: 무거운 연산이라 진짜 병렬 스레드(Worker Thread) 처리가 필요할 때만
@concurrent
func process(data: Data) async throws -> String {
// 이제 명시적으로 백그라운드 스레드 풀로 이동하여 실행됨
let result = performHeavyComputation(on: data)
return result
}
기존 Swift 6의 문제였던 에러가 발생한 지점을 처리하기 위해 전부 다 배워야 했던 문제
기본적으로 @MainActor에서 코드가 실행됨을 기본으로 처리함으로써 기존 코드에서 점진적으로 처리할 수 있도록 개선
https://www.swift.org/blog/swift-6.2-released
https://github.com/swiftlang/swift-evolution/blob/main/visions/approachable-concurrency.md