Actor — 개념, 데이터 격리 및 동시성

저자: IT Sectr 게시일: 2026-06-18 읽는 시간: 11 분

Actor는 Swift 5.5+에서 도입된 타입으로, 언어 수준에서 데이터 경합 문제를 해결합니다. 수동 잠금 및 DispatchQueue 대기열과 달리, actor는 자동으로 상태를 격리하고 액세스를 동기화합니다. 즉, 두 스레드가 동시에 actor 타입의 동일한 속성을 수정할 수 없으므로 개발자의 추가 노력 없이 경합 상태가 제거됩니다. 이 메커니즘은 컴파일러가 actor의 속성 및 메서드에 대한 액세스를 추적하는 actor 격리 개념을 기반으로 합니다. Apple, 2025에 따르면, actor 모델은 Swift 동시성 시스템의 근본적인 부분입니다.

핵심 사항

  • Actor는 컴파일러 수준에서 자동으로 상태를 데이터 경합으로부터 격리하는 타입입니다
  • 격리는 한 번에 하나의 스레드만 actor 인스턴스에 액세스함을 보장합니다
  • 호출은 async/await 메커니즘을 통해 비동기적으로 실행됩니다
  • Nonisolated를 사용하면 개별 actor 멤버에 대한 격리를 선택적으로 비활성화할 수 있습니다
  • Reentrancy는 actor에 재진입을 허용하여 교착 상태를 방지합니다

Swift에서 Actor란?

Actor는 클래스와 유사한 참조 타입이지만 데이터 경합에 대한 자동 보호 기능이 있습니다. Swift 5.5에서 async/await 및 Task와 함께 동시성 시스템의 일부로 도입되었습니다. Actor는 명시적 동기화 없이 여러 스레드에서 동시에 가변 상태를 읽거나 쓰지 않음을 보장합니다.

Actor 선언 구문

Actor를 선언하려면 actor 키워드를 사용하고 그 뒤에 멤버가 포함된 중괄호를 사용합니다. 구문적으로 actor는 클래스와 유사하지만 동작이 근본적으로 다릅니다.

swift
actor BankAccount {
    private var balance: Double
    
    init(initialBalance: Double) {
        self.balance = initialBalance
    }
    
    func deposit(amount: Double) {
        balance += amount
    }
    
    func getBalance() -> Double {
        return balance
    }
}

컴파일러는 모든 actor 속성과 메서드를 자동으로 격리하여 actor 컨텍스트 내에서만 액세스 가능하도록 합니다. actor 외부에서 balance에 액세스하려고 하면 호출이 async로 표시되지 않은 경우 컴파일러가 오류를 생성합니다.

Actor에서 데이터 격리 작동 방식

데이터 격리는 actor의 핵심 개념입니다. Actor는 actor 실행기 메커니즘을 통해 상태에 대한 상호 배타적 액세스를 보장합니다. 각 actor는 격리된 멤버에 대한 모든 액세스를 순차적으로 처리하는 자체 실행기를 가지고 있습니다.

격리 작동 방식

Actor 외부의 코드가 해당 메서드를 호출하면 호출이 actor 실행기 대기열에 배치됩니다. Actor는 한 번에 하나의 작업만 실행하여 경합이 없음을 보장합니다. 두 스레드가 동시에 deposit을 호출하면 두 번째 호출은 첫 번째 호출이 완료될 때까지 기다립니다.

swift
let account = BankAccount(initialBalance: 1000.0)
// 비동기 호출 — actor 외부에서 필요
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()

Actor 메서드에 대한 모든 호출에는 await가 필요합니다. actor가 다른 작업으로 바쁠 수 있기 때문입니다. 이것은 버그가 아니라 경합을 방지하기 위한 의도적인 설계입니다. Swift는 암시적으로 actor 속성의 getter와 setter를 비동기로 만들므로 속성을 읽는 데도 await가 필요합니다.

Actor와 Class의 주요 차이점

Actor와 클래스는 모두 참조 타입이지만 멀티스레드 환경에서의 동작은 크게 다릅니다. 클래스는 경합에 대한 자동 보호를 제공하지 않는 반면, actor는 타입 시스템을 통해 컴파일러 수준에서 이를 내장합니다.

특성ActorClass
경합 보호자동, 컴파일러 수준수동 동기화 필요
속성 액세스외부에서 await를 통해서만직접, 동기화 없음
상속다른 actor에서만표준 클래스 기반
프로토콜 준수프로토콜 준수 가능표준
성능격리로 인한 낮은 오버헤드동기화 없이 더 빠름

Actor 상속

Actor는 다른 actor에서만 상속받을 수 있으며 클래스에서는 상속받을 수 없습니다. 이는 의도적인 것으로, 클래스에는 actor 격리 메커니즘이 없고 타입을 혼합하면 안전 보장이 깨지기 때문입니다.

swift
actor SavingsAccount: BankAccount {
    func applyInterest(rate: Double) {
        let interest = balance * rate
        balance += interest
    }
}

Actor 메서드의 비동기 호출

비동기 호출은 외부 코드에서 actor와 상호 작용하는 메커니즘입니다. Actor가 상태를 격리하므로 외부에서 멤버에 액세스하려면 await가 필요합니다. 이를 통해 Swift는 호출 코드가 스레드를 차단하지 않고 actor가 다른 요청을 처리할 수 있음을 보장합니다.

전역 및 지역 Actor

선언된 actor 타입 외에도 Swift는 전역 actor — @MainActor 속성을 지원합니다. 이는 클래스, 속성 또는 메서드를 기본 스레드에서 실행 가능한 것으로 표시합니다. 이는 iOS 애플리케이션의 UI 코드에 특히 유용합니다.

swift
@MainActor
class ViewModel: ObservableObject {
    @Published var title: String = ""
    
    func updateTitle() {
        // 이 코드는 기본 스레드에서 실행되는 것이 보장됩니다
        title = "New Title"
    }
}

@MainActor를 사용하면 수동으로 DispatchQueue.main.async를 호출할 필요가 없어 코드가 더 깔끔하고 안전해집니다. 컴파일러는 기본 스레드로의 전환이 올바르게 발생하는지 확인합니다.

Nonisolated 및 Actor-Isolated 컨텍스트

Nonisolated는 actor의 메서드 또는 계산된 속성을 격리되지 않은 것으로 표시할 수 있는 키워드입니다. 이러한 멤버는 actor 격리 상태에 액세스할 수 없지만 actor 외부에서 await 없이 호출할 수 있습니다.

Nonisolated 함수

Nonisolated 메서드는 actor의 가변 상태에 의존하지 않는 계산에 유용합니다. 예를 들어 formatBalance 메서드는 balance에 직접 액세스하지 않고 전달된 값의 형식만 지정합니다. 이러한 메서드는 nonisolated로 만들어도 안전합니다.

swift
actor BankAccount {
    private var balance: Double = 0
    
    nonisolated func formatBalance(amount: Double) -> String {
        return "$\(amount)"
    }
}

Nonisolated 멤버는 동기적으로 실행되며 await가 필요하지 않습니다. 그러나 actor 격리 속성을 직접 읽을 수는 없습니다. Nonisolated 메서드가 actor의 값이 필요한 경우 매개변수로 전달되어야 합니다.

Reentrancy 및 재진입

Reentrancy는 비동기 호출을 기다리는 동안 actor에 재진입을 허용하는 메커니즘입니다. Reentrancy가 없으면 한 메서드가 다른 메서드를 기다리고 그 메서드가 다시 첫 번째 메서드를 기다리는 경우 actor가 영원히 교착 상태에 빠질 수 있습니다.

Reentrancy 작동 방식

Actor 내부의 코드가 await를 실행하면 actor는 현재 작업을 일시 중단하고 대기열에 있는 다른 작업을 처리할 수 있습니다. Await가 완료되면 작업이 재개됩니다. 이렇게 하면 교착 상태가 방지되지만 주의가 필요합니다. await 지점 사이에서 actor 상태가 변경될 수 있습니다.

swift
actor DataProcessor {
    var cache: [Int: String] = [:]
    
    func process(id: Int) async -> String {
        if let cached = cache[id] {
            return cached
        }
        // await — 재진입 지점
        let result = await fetchData(id: id)
        // await 후 캐시가 변경되었을 수 있습니다 — 다시 확인하세요
        cache[id] = result
        return result
    }
}

개발자는 재진입을 고려하고 await 지점 이후에 actor 상태를 확인해야 합니다. 일반적인 실수는 actor 격리가 비동기 일시 중단 지점을 통해 지속된다고 가정하는 것입니다. 실제로는 await와 다음 명령문 사이에 상태가 예상과 다를 수 있습니다.

자주 묻는 질문

Swift에서 Actor는 클래스와 어떻게 다른가요?

Actor는 자동으로 데이터 경합으로부터 상태를 격리하며 외부 액세스에 await가 필요합니다. 클래스는 이러한 보호를 제공하지 않습니다 — 개발자는 잠금 또는 대기열을 통한 동기화를 담당합니다. Actor는 actor에서만 상속받고, 클래스는 클래스에서 상속받습니다.

Actor에서 상속받을 수 있나요?

네, actor는 다른 actor에서 상속받을 수 있습니다. 하위 클래스는 부모의 모든 격리된 속성과 메서드를 상속받습니다. Actor는 클래스에서 상속받을 수 없습니다. 클래스는 컴파일러 수준에서 actor 격리 메커니즘이 없기 때문입니다.

actor-isolated 컨텍스트란 무엇인가요?

actor-isolated 컨텍스트는 actor의 가변 상태에 대한 직접 액세스가 허용되는 코드 영역입니다. 격리된 것으로 표시된 (기본값) actor 메서드 내에서는 await 없이 속성을 읽고 쓸 수 있습니다. 컴파일러가 격리 경계를 확인합니다.

Actor에서 외부 코드로 데이터를 전달하는 방법은?

Actor의 데이터는 Sendable 타입을 반환하는 비동기 메서드를 통해 전달되거나, 매개변수로 값을 받는 nonisolated 메서드를 통해 전달됩니다. Sendable 구조체로 actor 상태의 스냅샷을 반환하는 async 속성을 만들 수도 있습니다.

Actor는 프로토콜 준수를 지원하나요?

네, actor는 프로토콜을 준수할 수 있습니다. 프로토콜에 격리 요구 사항(actor-isolated)이 포함된 경우 자동으로 actor 격리가 됩니다. 프로토콜의 비동기 메서드의 경우, isolated 마커를 사용하여 특정 actor에서 호출되어야 함을 지정할 수 있습니다.

요약

  • Actor는 자동 데이터 격리를 갖춘 참조 타입으로, Swift 5.5+에서 도입됨
  • 격리는 요청을 순차적으로 처리하는 actor 실행기를 통해 작동합니다
  • 외부 액세스에는 await가 필요합니다 — 이는 경합이 없음을 보장합니다
  • Nonisolated를 사용하면 격리된 상태에 액세스하지 않는 동기 메서드를 선언할 수 있습니다
  • 전역 actor(@MainActor)는 격리를 전체 실행 스레드로 확장합니다
  • Reentrancy는 await 중에 actor에 재진입을 허용하여 교착 상태를 방지합니다

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기