Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

Jupyo's Daily Story

@MainActor 본문

Swift

@MainActor

JangJupyo 2025. 6. 5. 12:29
728x90
반응형

@MainActor는 Swift의 새로운 동시성(Concurrency) 모델의 핵심 부분으로, 특히 UI 업데이트와 같이 반드시 메인 스레드에서 실행되어야 하는 코드를 안전하고 효율적으로 관리하기 위해 도입되었습니다.

 

@MainActor란 무엇인가?

@MainActor는 Swift 5.5에 도입된 글로벌 액터(Global Actor)의 한 종류입니다. 액터(Actor)는 공유 가능한 변경 가능한 상태(mutable shared state)에 대한 동시 접근으로 인한 데이터 경쟁(data race)을 방지하기 위한 동시성 모델의 한 기능입니다.

 

@MainActor는 특별한 글로벌 액터로, 그 이름에서 알 수 있듯이 앱의 메인 스레드에 연결된 액터입니다. 즉, @MainActor로 표시된 모든 코드 블록, 함수, 프로퍼티 등은 반드시 메인 스레드에서 실행됨을 보장합니다.

 

왜 @MainActor가 필요한가?

iOS, macOS, watchOS, tvOS 등 Apple 플랫폼의 앱은 UI 업데이트를 메인 스레드에서만 수행해야 한다는 엄격한 규칙이 있습니다. 만약 다른 스레드에서 UI를 업데이트하려고 하면, 예측 불가능한 동작, UI 깨짐, 심지어 앱 크래시가 발생할 수 있습니다.

 

과거에는 DispatchQueue.main.async { ... } 와 같은 GCD(Grand Central Dispatch)를 사용하여 UI 업데이트를 메인 스레드에 명시적으로 디스패치해야 했습니다. 그러나 Swift Concurrency가 도입되면서, async/await를 사용하여 비동기 코드를 작성할 때, 특정 코드가 메인 스레드에서 실행되어야 한다는 것을 컴파일러에게 알려주는 더 타입-세이프(type-safe)하고 통합된 방법이 필요해졌습니다.

 

@MainActor는 이 문제를 해결해 줍니다. 개발자가 명시적으로 DispatchQueue.main.async를 호출할 필요 없이, @MainActor만 붙이면 컴파일러가 해당 코드가 메인 스레드에서 실행되도록 보장해 줍니다. 필요한 경우 자동으로 스레드 전환(context switch 또는 "hop" 이라고 불림)을 처리합니다.

 

@MainActor의 작동 방식

@MainActor는 다음 두 가지 주요 방식으로 작동합니다.

  • 컴파일 타입 검사 : @MainActor로 표시되는 함수나 프로퍼티가 메인 액터와 격리되지 않은(non-isolcated) 컨텍스트에서 호출될 때, 컴파일러는 경고 또는 오류를 발생시켜 개발자에게 메인 스레드에서 실행될 필요가 있음을 알려줍니다. 이는 런타임 오류를 사전에 방지하는 데 큰 도움이 됩니다.
  • 런타임 보장 : @MainActor로 표시된 코드가 실행될 때, Swift 런타임은 해당 코드가 메인 스레드에서 실행되고 있음을 확인합니다. 만약 메인 스레드가 아닌 다른 스레드에서 호출되었다면, 자동으로 메인 스레드로 작업을 디스패치하여 실행을 보장합니다.

 

@MainActor의 적용 대상

@MainActor는 다음과 같은 곳에 적용할 수 있습니다.

  • 함수 및 메서드 : 특정 함수나 메서드 내에 모든 코드가 메인 스레드에서 실행되도록 지정합니다.
@MainActor func updateUI() {
    // 이 코드는 항상 메인 스레드에서 싱행됩니다.
    myLabel.text = "새로운 텍스트"
}

 

  • 프로퍼티 : 특정 프로퍼티에 대한 접근(읽기/쓰기)이 메인 스레드에서만 이루어지도록 강제합니다.
@MainActor var userName:  String = ""

func fetchUserData() async {
    let fetchedName = await networkCallForUserName()
    // userName 프로퍼티는 @MainActor로 표시되어 있으므로,
    // 이 할당은 자동으로 메인 스레드에서 이루어집니다.
    userName = fetchedName
}

 

  • 클래스, 구조체, 열거형 (전체 타입) : 타입 전체에 @MainActor를 적용하면 해당 타입의 모든 저장 프로퍼티와 메서드(초기화 제외)가 자동으로 메인 액터에 격리됩니다. 이는 특히 ObserableObject를 채택하는 ViewModel 등에서 유용합니다.
@MainActor class MyViewModel: ObserableObject {
    @Published var data: [String = []
    
    func loadData() async {
        // 이 함수는 @MainActor에 격리되어 있으므로,
        // data 프로퍼티에 접근하는 것은 안전하며 메인 스레드에서 이루어집니다.
        data = await fetchDataFromNetwork()
    }
}

 

  • 클로저 : 특정 클로저 블록이 메인 스레드에서 실행되도록 지정할 수 있습니다.
Task {
    // 백그라운드에서 작업 수행
    let result = await someLongRunningCalculation()
    
    // UI 업데이트를 위해 클로저를 메인 액터에 격리
    await MainActor.run {
        // 이 블록은 메인 스레드에서 실행됩니다.
        self.updateUIAfterCalculation(result)
    }
}

 

또는 Task의 클로저를 @MainActor에 격리시킬 수도 있습니다.

Task { @MainActor in
    // 이 Task의 모든 코드는 메인 스레드에서 시작됩니다.
    // 다만, 여기서 await 하는 비동기 함수가 다른 액터에 격리되어 있다면
    // 해당 함수 실행 중에는 다른 스레드로 "hop"할 수 있습니다.
    // 하지만 해당 함수가 끝나고 다시 이 Task로 돌아올 때는
    // 다시 메인 스레드로 돌아옵니다.
    let data = await someNetworkRequest()
    myLabel.text = data
}

 

  • MainActor.run()과 @MainActor 어트리뷰트
    Swift Concurrency는 MainActor의 run() 메서드를 제공합니다. 이는 특정 코드 블록을 메인 액터에서 실행하도록 강제하는데 사용됩니다.
func processDataAndUI() async {
    let rawData = await fetchRawDataInBackground() // 백그라운드 작업
    
    await MainActor.run {
        // 이 클로저 안의 코드는 메인 스레드에서 실행됨
        self.updateUIWithData(rawData)
    }
}

 

@MainActor 어트리뷰드는 선언 시점에 적용하여 해당 선언 전체가 메인 액터에 격리되도록 하는 반면, MainActor.run은 코드 실행 중에 특정 클로저만 메인 액터에서 실행되도록 합니다. 둘 다 메인 스레드 실행을 보장하지만, 적용 범위와 시점이 다릅니다.

 

중요한 고려 사항 (주의할 점)

  • 컴파일러의 똑똑함 : Swift 컴파일러는 @MainActor의 규칙을 매우 잘 이해하고 있습니다. 만약 @MainActor로 표시된 프로퍼티나 함수를 메인 액터에 격리되지 않은 컨텍스트에서 접근하려고 하면, 컴파일러가 오류나 경고를 발생시켜 데이터 경쟁을 방지하도록 돕습니다.
  • async와의 관계 : @MainActor로 표시된 async 함수는 해당 함수가 시작될 때 메인 스레드에서 실행됨을 보장합니다. 하지만 함수 내부에서 await 호출을 통해 다른 비동기 함수를 호출하는 동안에는 다른 스레드로 "hop"할 수 있습니다. await 호출이 완료되고 해당 함수로 돌아올 때 다시 메인 스레드로 돌아옵니다.
  • 성능 : 모든 작업을 메인 스레드에서 수행하는 것은 앱의 응답성을 저하시킬 수 있습니다. @MainActor는 오직 UI 업데이트나 메인 스레드에만 의존하는 작업에만 사용해야 합니다. 네트워크 요청, 데이터 처리 등 시간이 오래 걸리는 작업은 @MainActor가 아닌 다른 액터나 비동기 함수에서 백그라운드로 처리하는 것이 좋습니다.
  • 전이성(Transitivity) : @MainActor로 표시된 타입(클래스, 구조체 등) 내부의 모든 저장 프로퍼티와 메서드(예외 있음)는 자동으로 @MainActor에 격리됩니다. 즉, 해당 타입의 인스턴스를 통해 접근하는 모든 멤버는 메인 스레드에서 실행되어야 합니다.

 

@MainActor는 Swift Concurrency를 사용하여 안전하고 효율적인 비동기 코드를 작성하는 데 필수적인 도구이며, 특히 UI 관련 작업에서 그 진가가 발휘됩니다.

반응형

'Swift' 카테고리의 다른 글

mutating  (0) 2025.06.16
고차 함수(Higher-Order Functions)  (0) 2025.06.12
프로토콜 (Protocols)  (0) 2024.11.10
static, class, final class 프로퍼티/메서드  (2) 2024.10.26
생명주기 (Lifecycle)  (2) 2024.10.14