일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- git 명령어
- SwiftUI
- swfitui
- 동시성 프로그래밍
- environment value
- actor
- assosiated type
- MainActor
- environment object
- task 취소
- REDRAW
- NavigationLink
- 스레드 점유권
- ObservedObject
- MVVM
- rest api
- Git
- unowned
- restful api
- async/await
- 순환참조
- 작업 취소
- weak
- StateObject
- navigationview
- Access Control
- 앱실행
- Swift
- 격리 시스템
- Swift Concurrency
- Today
- Total
Develup
[Swift] @MainActor: 동시성 환경에서 UI 업데이트를 안전하게 처리하는 방법 본문
Swift의 동시성 모델에서 UI 관련 코드는 특별한 주의가 필요합니다. 여러 백그라운드 태스크가 동시에 UI를 업데이트하려고 하면 예측할 수 없는 동작이나 크래시가 발생할 수 있기 때문입니다. Swift의 MainActor는 이러한 문제를 해결하기 위한 핵심 도구입니다.
MainActor는 Swift의 동시성 프레임워크에서 UI 업데이트를 메인 스레드에서 안전하게 처리할 수 있도록 도와주는 전역 액터입니다. 앱의 사용자 인터페이스는 항상 메인 스레드에서 업데이트되어야 하는데, MainActor는 이 규칙을 코드 수준에서 적용하도록 도와줍니다.
이 글에서는 MainActor의 개념, 작동 방식, 그리고 실제 코드에서 어떻게 활용할 수 있는지 알아보겠습니다.
MainActor란 무엇인가?
MainActor는 Swift의 액터 시스템의 일부로, 메인 스레드에서 실행되어야 하는 코드를 안전하게 관리하는 특별한 액터입니다. 액터는 동시성 환경에서 상태를 보호하는 Swift의 중요한 도구입니다.
@MainActor func updateUI() {
// 이 함수 내부의 모든 코드는 메인 스레드에서 실행됨을 보장
label.text = "업데이트 완료"
button.isEnabled = true
}
MainActor의 핵심 역할은 UI 업데이트 코드가 항상 메인 스레드에서 실행되도록 보장하는 것입니다. 이는 다음과 같은 이점을 제공합니다:
- 컴파일 타임 안전성: 메인 스레드에서 실행되어야 하는 코드를 컴파일 시점에 확인
- 명시적 의도 표현: 어떤 코드가 UI를 업데이트하는지 명확하게 표현
- 스레드 안전성: UI 관련 코드의 스레드 안전성 보장
- 코드 가독성 향상: UI 관련 코드임을 명확히 표시
MainActor는 어떻게 작동하는가?
MainActor는 내부적으로 메인 디스패치 큐를 사용하여 작업을 메인 스레드에서 실행합니다. 그러나 기존의 DispatchQueue.main.async와 달리, MainActor는 Swift의 동시성 모델과 통합되어 더 안전하고 편리한 방식으로 작동합니다.
// 기존 방식
DispatchQueue.main.async {
self.label.text = "완료"
}
// MainActor 사용
await MainActor.run {
self.label.text = "완료"
}
MainActor는 일반 Actor와 마찬가지로 동시 접근으로부터 상태를 보호하지만, 유일한 차이점은 MainActor의 모든 코드가 메인 스레드에서 실행된다는 것입니다. 이는 UI 업데이트가 항상 메인 스레드에서 이루어져야 한다는 iOS, macOS 등의 플랫폼 요구사항을 자연스럽게 만족시킵니다.
MainActor의 사용 방법
MainActor는 다양한 수준에서 적용할 수 있습니다. 함수, 프로퍼티, 클래스, 구조체 등에 @MainActor 속성을 적용할 수 있습니다.
함수에 MainActor 적용하기
UI를 업데이트하는 개별 함수에 @MainActor를 적용할 수 있습니다:
@MainActor func updateUserInterface(with data: UserData) {
nameLabel.text = data.name
ageLabel.text = "\(data.age)"
profileImageView.image = data.profileImage
}
// 사용 시에는 await 키워드 필요
await updateUserInterface(with: userData)
이 함수는 @MainActor 어노테이션으로 인해 항상 메인 스레드에서 실행됩니다. 함수를 호출할 때는 await 키워드를 사용해야 합니다.
클래스나 구조체에 MainActor 적용하기
UI 관련 작업을 주로 하는 클래스나 구조체 전체에 @MainActor를 적용할 수도 있습니다:
@MainActor
class ProfileViewController: UIViewController {
private let nameLabel = UILabel()
private let bioLabel = UILabel()
// 이 클래스의 모든 메서드는 자동으로 메인 스레드에서 실행됨
func updateProfile(with user: User) {
nameLabel.text = user.name
bioLabel.text = user.bio
}
func startAnimation() {
UIView.animate(withDuration: 0.3) {
self.nameLabel.alpha = 1.0
}
}
}
클래스 전체에 @MainActor를 적용하면 해당 클래스의 모든 메서드와 프로퍼티가 메인 스레드에서 접근됨을 보장합니다. 이는 ViewController나 View 같은 UI 관련 클래스에 매우 유용합니다.
비동기 함수에서 MainActor 사용하기
비동기 함수에서 UI 업데이트를 안전하게 처리할 수 있습니다:
func fetchUserData() async throws {
// 백그라운드 스레드에서 네트워크 요청 수행
let userData = try await networkService.fetchUserProfile()
// UI 업데이트는 MainActor에서 수행
await MainActor.run {
usernameLabel.text = userData.username
bioLabel.text = userData.bio
followersCountLabel.text = "\(userData.followers) followers"
}
}
MainActor.run 메서드를 사용하면 클로저 내의 코드가 메인 스레드에서 실행됨을 보장합니다.
실제 활용 사례
MVVM 아키텍처에서의 MainActor
MVVM 아키텍처에서는 ViewModel이 Model 데이터를 처리하고 View에 표시할 형태로 변환합니다. ViewModel은 비즈니스 로직을 수행하지만, UI 업데이트를 트리거하므로 @MainActor로 보호하는 것이 좋습니다:
@MainActor
class UserProfileViewModel {
private(set) var username = ""
private(set) var bio = ""
private(set) var isLoading = false
func loadUserProfile() async throws {
isLoading = true // UI 상태 업데이트
// 백그라운드 작업 수행
let userData = try await userService.fetchUserData()
// UI 관련 상태 업데이트 (메인 스레드에서 자동으로 수행됨)
username = userData.name
bio = userData.bio
isLoading = false
}
}
이 ViewModel은 @MainActor로 선언되어 있어, 내부의 모든 상태 변경이 메인 스레드에서 이루어집니다. 이는 SwiftUI나 UIKit과 함께 사용할 때 스레드 관련 이슈를 방지합니다.
SwiftUI에서의 MainActor
SwiftUI에서는 @MainActor가 특히 중요합니다. SwiftUI의 ObservableObject와 함께 사용하면 UI 업데이트를 안전하게 처리할 수 있습니다:
@MainActor
class WeatherViewModel: ObservableObject {
@Published var temperature = ""
@Published var condition = ""
@Published var isLoading = false
func refreshWeather() async {
isLoading = true
do {
// 백그라운드에서 날씨 데이터 가져오기
let weatherData = try await weatherService.fetchCurrentWeather()
// UI 업데이트 (자동으로 메인 스레드에서 수행됨)
temperature = "\(weatherData.temperature)°C"
condition = weatherData.condition
} catch {
// 에러 처리
temperature = "Error"
condition = "Unavailable"
}
isLoading = false
}
}
주의사항 및 고려사항
- 성능 고려: @MainActor로 표시된 코드는 모두 메인 스레드에서 실행됩니다. 오래 걸리는 작업은 메인 스레드를 차단하고 UI를 멈추게 할 수 있으므로, 시간이 많이 소요되는 작업은 다른 액터나 Task에서 처리해야 합니다.
- 교착 상태 방지: 메인 액터 내부에서 다른 액터의 작업을 동기적으로 기다리면 교착 상태가 발생할 수 있습니다. 항상 비동기 접근을 사용하세요.
- 컨텍스트 인식: 이미 @MainActor 컨텍스트에 있다면, MainActor.run을 중첩해서 사용할 필요가 없습니다.
- 격리 규칙 이해: @MainActor로 표시된 코드는 메인 액터 외부에서 호출할 때 await가 필요합니다.
결론
MainActor는 Swift의 동시성 모델에서 UI 업데이트를 안전하게 처리하기 위한 강력한 도구입니다. 메인 스레드에서 실행되어야 하는 코드를 명시적으로 표시하고, 컴파일 시점에서 스레드 안전성 문제를 방지할 수 있습니다.
효과적인 MainActor 사용을 위한 핵심 사항:
- UI 관련 코드에는 @MainActor 어노테이션을 적용하세요
- ViewModel이나 UI 컨트롤러 같은 클래스 전체에 적용하는 것을 고려하세요
- 시간이 많이 소요되는 작업은 백그라운드에서 처리하고, 결과만 MainActor를 통해 업데이트하세요
- SwiftUI의 ObservableObject와 함께 사용하면 UI 업데이트가 더욱 안전해집니다
- 메인 스레드에서 실행되는 코드의 성능에 항상 주의하세요
MainActor를 적절히 활용하면 동시성 환경에서도 안전하고 예측 가능한 UI 업데이트를 구현할 수 있습니다. 이는 코드의 안정성을 높이고 버그를 줄이는 데 큰 도움이 됩니다.
'Swift > ETC' 카테고리의 다른 글
[Swift] Sendable 프로토콜: 안전한 동시성을 위한 완벽 가이드 (0) | 2025.03.13 |
---|---|
[Swift] Actor 격리 시스템: 동시성 환경에서 안전한 데이터 접근을 위한 핵심 메커니즘 (0) | 2025.03.11 |
[Swift] 제네릭과 어소시에이티드 타입: 차이점과 활용 가이드 (0) | 2025.03.06 |
[Swift] 메모리를 참조하는 방법(strong, weak, unowned) (0) | 2021.03.10 |
[Swift] ARC란? (0) | 2021.03.08 |