Swift Concurrency: nonisolated(nonsending)

틀틀보·2026년 5월 22일

Swift Concurency

목록 보기
13/13

기존 nonisolated async의 모호한 점

  • nonisolated 동기 함수: 호출한 Caller의 액터 컨텍스트를 그대로 이어받아 실행 (동일한 actor executor에서 실행)

  • nonisolated 비동기 함수: 호출한 쪽이 어떤 액터인지에 관계없이 Global Generic Executor로 컨텍스트 강제 전환

nonisolated(nonsending)

비동기 함수더라도 호출한 액터의 Executor 위에서 그대로 실행되도록 보장

  • 컨텍스트 유지: 함수가 시작, 일시 중단, 재개 될 때 모두 호출자의 액터 컨텍스트 내에서 실행

  • 격리 경계 없음: 호출자와 함수가 동일한 액터 공간을 공유하므로, 인자나 결과값이 격리 경계를 넘지 X
    결과적으로 Sendable 구조체가 아니거나 영역 격리(Region Isolation)를 통과하지 못하는 객체도 아무런 경고 없이 안전하게 전달 가능

import Foundation
// Sendable을 따르지 않는 일반 클래스
class RegularUser {
    var name: String
    init(name: String) { self.name = name }
}

// [기존 방식] 글로벌 실행기로 컨텍스트가 전환되는 비동기 함수 
// (6.2 이전의 기본 동작 방식 @concurrent)
@concurrent func oldAsyncFunction(_ user: RegularUser) async {
    // 실행되자마자 글로벌 스레드 풀로 이동
    try? await Task.sleep(for: .seconds(0.1))
    print(user.name)
}

// [Swift 6.2] 호출자의 컨텍스트를 그대로 유지하는 비동기 함수
nonisolated(nonsending) func newAsyncFunction(_ user: RegularUser) async {
    // 비동기 함수이지만 글로벌 스레드 풀로 이동하지 않고, 호출한 액터 위에서 그대로 실행
    try? await Task.sleep(for: .seconds(0.1))
    print(user.name)
}

actor Dashboard {
    var user = RegularUser(name: "Chul")
    
    func runTasks() async {
        // ❌ 컴파일 에러 발생
        // 이유: oldAsyncFunction은 글로벌 스레드 풀로 넘어가기 때문에 
        // Non-Sendable 객체인 'user'를 안전하게 넘기기 불가
        await oldAsyncFunction(user) 
        
        // newAsyncFunction은 Dashboard 액터의 스레드 공간 안에서 그대로 실행
        // 격리 경계를 넘어가지 않으므로 Non-Sendable 객체를 그대로 전달해도 안전
        await newAsyncFunction(user)
    }
}

NonIsolatedNonSendingByDefault

프로젝트 설정으로 모든 nonisolated async 함수가 기본값으로 nonisolated(nonsending)으로 동작하도록 변경

이전처럼 Global Generic Executor로 동작하게 하고 싶다면?

@concurrent

해당 함수는 호출되는 액터로부터 항상 벗어나 실행되어야 함을 명시하는 키워드

@concurrent
func concurrentAsync(_ data: MyClass) async { }

Task { @concurrent in }

Task.detached와 Task { @concurrent in }

두 개 모두 액터 격리에서 벗어나 Global Generic Executor에서 실행된다는 점은 동일.

호출한 부모로부터 무엇을 상속받는가의 차이가 있음.

  • Task { @concurrent in }: 호출자의 액터 Executor 상속만 명시적으로 거부하고, Task-Local 값이나 우선순위 등 다른 태스크 컨텍스트는 그대로 상속

  • Task.detached { }: 호출한 컨텍스트와 완전히 단절된 독립적인 최상위 태스크를 생성. 액터 격리는 물론, Task-Local 값도 전혀 상속 X

참고

[Swift] Swift 6.2 의 nonisolated(nonsending) 과 @concurrent (naljin)

SE-0461: Async Function Isolation (nonisolated(nonsending))

SE-0302: Sendable and @Sendable closures

Apple Developer Documentation - Task

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

0개의 댓글