Swift Concurrency: Approchable Concurrency

틀틀보·2026년 5월 17일

Swift Concurency

목록 보기
12/13

Approchable Concurrency

기존 엄격한 동시성 검사와 처리에 관해 개발자의 피로감을 해소시키기 위해 개선된 동시성

무엇이 문제였는가?

1. 가파른 학습 곡선 및 에러 폭탄

기존 코드에서 Swift 6로 전환 시, 수많은 Sendable 경고 + Actor 격리 에러 발생으로 인한 처리 피로감 발생

2. 격리 전파로 인한 연쇄 현상

예를 들어 UI 코드 일부에 @MainActor를 붙이면, 그와 연결된 모든 프로토콜, 콜백, 클로저에 연쇄적으로 @MainActor@Sendable을 붙여야 하는 도배 현상 발생

3. 불필요한 스레드 전환 오버헤드

  • 특정 상태에 격리되지 않은(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)
}

어떻게 개선했을까?

1. Single-threaded by default

  • 코드는 기본적으로 단일 스레드에서 실행된다고 가정

  • 동시 접근이 없는 순차 실행 모델을 기본 (하나의 Actor에서 순차 처리)

  • -default-isolation MainActor 컴파일러 옵션을 적용하면 작성된 코드들이 기본적으로 @MainActor에서 격리 처리될 수 있도록 추가

  • 코드는 @MainActor에서 순차적으로 처리되도록 하되, 필요에 따라 비동기, 병렬성 코드를 점진적으로 추가할 수 있도록 개선

  • 기존에 돌아가던 코드들이 돌아갈 수 있도록 하고 점진적으로 개선할 수 있게 됨.

2. Intuitive async functions

  • 이전에는 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를 그대로 유지함.
}

3. Opting into concurrency

  • @concurrent 속성을 통해 명시적으로 병렬 실행 허용을 명시

  • 항상 다른 스레드에서 실행되는 건 아님.

  • 기본적으로 호출자 컨텍스트를 유지하는 방향으로 바뀌었기 때문에, 무거운 작업을 실제로 병렬 스레드 풀로 보내고 싶을 때 사용하는 새로운 속성

  • 개발자의 직렬, 병렬 실행에 관한 의도를 명확하게 표시


Swift의 점진적 공개

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://medium.com/@mini-min/swift-5%EB%B6%84-%EC%95%88%EC%97%90-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-approachable-concurrency-47472a3312eb

https://www.swift.org/blog/swift-6.2-released

https://github.com/swiftlang/swift-evolution/blob/main/visions/approachable-concurrency.md

https://developer.apple.com/videos/play/wwdc2025/268

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

0개의 댓글