이번 편의 목표: 화면을 바꾸는 코드는 메인 액터에 두고, 끝나기를 기다려야 하는 함수는 async로 표시합니다.

스위프트 6 언어 모드는 데이터 경합을 컴파일 때 거절합니다. 같은 저장소를 서로 다른 작업이 동시에 바꾸면 결과가 실행마다 달라지므로, 컴파일러는 그 공유가 안전하지 않으면 빌드를 멈춥니다.

메인 액터는 화면 갱신이 모이는 직렬 구간입니다. 그 안의 코드는 한 번에 하나씩 실행됩니다. SwiftUI의 뷰는 이 구간에 있습니다.

Swift
@MainActor
struct LibraryModel {
    var title = "개미"

    func fee(days: Int) -> Int {
        days * 100
    }
}

@MainActor
func showFee() {
    let model = LibraryModel()
    print(model.fee(days: 3))
}

await showFee()

출력은 300입니다. showFee와 LibraryModel이 같은 메인 액터에 있으므로 fee 호출은 추가 대기 없이 이어집니다. await는 showFee가 끝날 때까지 이 호출 지점이 기다린다는 표시입니다.

시간이 걸리는 함수는 async를 붙입니다. 호출자는 await로 결과를 받습니다.

Swift
func loadTitle() async -> String {
    try? await Task.sleep(for: .milliseconds(200))
    return "공책"
}

스위프트 6.2 이후의 기본 방향은, 따로 표시하지 않은 코드가 갑자기 다른 실행 구간으로 옮겨 가지 않는 것입니다. Xcode에서 새로 만드는 앱은 주석이 없는 코드를 메인 액터로 두는 설정이 켜져 있는 경우가 많습니다. 기존 프로젝트는 예전처럼 격리가 없는 상태가 기본일 수 있습니다. 패키지에서 같은 기본을 켜려면 12편의 defaultIsolation 설정을 넣습니다.

백그라운드가 정말 필요할 때만 그 함수를 메인 액터 밖으로 보냅니다. 화면의 title을 백그라운드 함수에서 직접 바꾸지 않고, await로 받은 값을 메인 액터의 함수에서 넣습니다.

직접 해 보기

fee에 5를 넘겨 500이 출력되는지 확인하세요. loadTitle의 결과를 await로 받아 print하세요.

해설

5 곱하기 100은 500입니다. loadTitle은 약 0.2초를 기다린 뒤 공책을 반환합니다. try?는 대기 중 취소되면 nil을 만들고, 이 예제에서는 취소하지 않으므로 공책이 반환됩니다.