요즘 Swift Concurrency에 대해서 공부를 하고 있는 중인데 그 중 MainActor의 역할이 아주 중요하게 작용하는 것 같아 한번 정리하고 넘어가고자 한다!!

https://developer.apple.com/documentation/swift/mainactor/
MainActor | Apple Developer Documentation
A singleton actor whose executor is equivalent to the main dispatch queue.
developer.apple.com
기본적으로 MainActor는 메인 스레드에서 실행되어야 하는 코드가 백그라운드 스레드에서 실행되지 않도록 보장하는 역할을 담당한다.
이 역할과 동작방식에 대해서 한번 자세히 알아보도록 하자
MainActor란?
MainActor는 코드가 메인스레드에서 실행되도록 보장하는 Swift의 동시성 속성이다. 이를 통해 UI 업데이트 작업을 백그라운드 스레드에서 실행하는 실수를 방지하고, 안정적인 동작을 보장할 수 있다.
쉽게 얘기하자면 메인스레드에서 실행되어야 하는 작업을 자동으로 처리해주는 ‘경비원’ 이라고 생각하면 될 것 같다. UI 업데이트가 백그라운드 스레드에서 실행되면 앱이 충돌하거나 버그가 발생할 수 있는데 이를 사전에 방지하는 역할을 MainActor가 담당한다.
MainActor가 중요한 이유!
UI업데이트와 같은 메인스레드에서 실행되지 않으면 안되는 요소들이 메인스레드에서 실행되지 않았을때엔 UI업데이트 시 백그라운드에서 충돌이 발생한다거나 UI가 깜빡이거나 반응하지 않는 현상이 나타나게 된다. 또한, 예상치 못한 버그로 인해 앱이 정상적으로 동작하지 않을 수도 있다.
이런 문제를 피하기 위해서 보통 DispatchQueue.main.async를 사용했지만, Swift의 MainActor를 사용하면 더욱 간결하고 안정하게 사용할 수 있게 된다.
MainActor 사용법
그렇다면 이제 MainActor를 사용하는 법에 대해서 알아보자!!
* 함수에 @MainActor 속성 사용
가장 기본적인 방법은 함수에 @MainActor 속성을 추가하는 것이다.
@MainActor
func updateUI() {
label.text = "업데이트"
tableView.reloadData()
}
이렇게 테이블뷰를 리로드하고 레이블의 텍스트를 변경하는 험수에 MainActor를 사용하면 이 함수는 항상 메인스레드에서 실행됨을 보장받게 된다.
이렇게 사용할 경우 DispatchQueue.main을 사용하지 않아도 되므로 더욱 간결하고 깔끔한 코드가 될 수 있다.
* 특정 코드 블록에만 메인 스레드에서 실행(MainActor.run)
일시적으로 메인 스레드에서 실행해야 하는 경우 MainActor.run {}을 사용할 수 있다.
Task {
let data = await fetchData()
await MainActor.run {
self.updateUI(with: data)
}
}
이렇게 특정 부분에 사용하게 되면 UI 업데이트 작업만 메인 스레드에서 실행할 수 있게 된다.
MainActor 위에 fetchData의 경우는 백그라운드에서 실행되므로 성능을 최적화 할 수 있다.
클래스/구조체 전체를 @MainActor로 지정하기
뷰 모델과 같은 UI 관련 로직을 처리하는 클래스는 전체 클래스를 @MainActor로 지정하는 것이 좋다.
@MainActor
class CounterViewModel: ObservableObject {
@Published var count = 0
func increase() {
count += 1
}
}
이렇게 MainActor를 사용하면 이클래스 내 모든 속성과 메서드는 항상 메인 스레드에서 실행되게 된다.
SwiftUI와 같이 UI 업데이트가 중요한 경우 특히 유용하게 사용된다!!
MainActor 사용 시 주의할 점
MainActor는 모든 작업을 메인 스레드에서 실행하므로, CPU 집약적인 작업이 포함되면 성능 저하를 유발할 수 있다.
@MainActor
func processLargeDataSet() {
for _ in 0..<1_000_000 {
// CPU를 많이 사용하는 작업
}
}
이와 같이 대량 데이터를 처리해야하는 무거운 연산 작업을 @MainActor에서 실행하면 앱이 느려지거나 멈출 수 있다!
이를 해결하기 위해서는
func processLargeDataSet() async {
let result = await Task.detached {
// CPU를 많이 사용하는 작업
return performHeavyCalculation()
}.value
await MainActor.run {
self.updateUI(with: result)
}
}
CPU 연산은 백그라운드에서 처리하도록 하고, UI 업데이트만 메인 스레드에서 실행하도록 최적화 하는 방식이 필요하다.
MainActor의 내부 동작 방식
- MainActor는 글로벌 액터이다.
- Swift에서 액터는 동시성을 관리하는 단위이다.
- MainActor는 특별한 글로벌 액터로 항상 메인 스레드에서 실행된다.
- MainActor는 Race Condition을 방지한다!
- 액터는 내부적으로 단일 실행 큐를 사용해 경쟁 조건을 방지한다.
- 이를 통해 UI 업데이트가 항상 올바른 순서로 실행되도록 한다!!
마지막으로 정리하면 MainActor를 사용하게 되면 UI 업데이트를 항상 안전하게 실행할 수 있게 되고, DispatchQueue.main.async 없이도 자동으로 메인 스레드에서 실행된다. 또한 비동기 코드와 통합하여 가독성이 향상되고 Race Condition을 방지하는 역할을 할 수 있다!
이렇게 Concurrency 중에서 큰 역할을 담당하는 MainActor에 대해서 알아봤다.. 요즘 동시성 공부중인데 생각보다 어려운 부분이 있는 것 같아서 주기적으로 기록하면서 공부할 예정.. 오늘은 여기까지!
'◽️ Programming > iOS' 카테고리의 다른 글
Swift Concurrency에서 Task는 어떤 역할을 담당하나 (0) | 2025.02.20 |
---|---|
Objective-C를 Swift에 가져와 사용하기 (0) | 2025.02.12 |
Vision 프레임 워크를 활용해 얼굴 인식하기 (0) | 2025.01.20 |
JWT의 기본 개념과 HaruFit 프로젝트에 적용해보기 (0) | 2025.01.12 |
Swift Format , Swift Lint 프로젝트에 적용하기 (0) | 2025.01.07 |