반응형
SwiftUI에서 많이 하는 실수 - UI 업데이트를 메인 스레드에서 하지 않아 생기는 문제
SwiftUI는 UIKit과 마찬가지로 모든 UI 업데이트가 메인 스레드에서 일어난다고 가정합니다.
비동기 코드나 백그라운드 큐에서 @State나 @Published 값을 변경하면,
경고가 뜨거나 레이아웃이 깨지고, 드물게는 크래시로 이어질 수 있습니다.
1. 문제 원인
- URLSession, DispatchQueue.global, Task 등에서 직접 상태를 수정
- Swift Concurrency의 Actor, MainActor 개념을 정확히 이해하지 못함
- “어차피 SwiftUI가 알아서 메인 스레드로 바꿔주겠지”라고 생각
2. 나타나는 증상
- 콘솔에 다음과 같은 경고가 출력됨
> Publishing changes from background threads is not allowed… - UI가 간헐적으로 업데이트되지 않거나, 한 프레임 늦게 반응
- 드물게는 크래시나 레이아웃 깨짐 현상
3. 잘못된 코드 예시
final class CardsViewModel: ObservableObject {
@Published var cards: [Card] = []
func load() {
DispatchQueue.global().async {
let result = fetchFromNetwork() // 백그라운드에서 로딩
// ❌ 백그라운드 큐에서 바로 @Published 수정
self.cards = result
}
}
}
또는 async/await 사용 시:
func load() async {
let result = await fetchFromNetworkAsync()
// ❌ 호출한 쪽의 스레드를 신경 쓰지 않고 바로 대입
self.cards = result
}
4. 올바른 코드 예시
4-1. DispatchQueue.main을 사용
final class CardsViewModel: ObservableObject {
@Published var cards: [Card] = []
func load() {
DispatchQueue.global().async {
let result = fetchFromNetwork()
DispatchQueue.main.async {
self.cards = result // ✅ 메인 큐에서 업데이트
}
}
}
}
4-2. @MainActor 사용 (Swift Concurrency)
@MainActor
final class CardsViewModel: ObservableObject {
@Published var cards: [Card] = []
func load() async {
let result = await fetchFromNetworkAsync()
cards = result // ✅ MainActor 보장
}
}
또는 메서드 단위에만 MainActor 적용:
final class CardsViewModel: ObservableObject {
@Published var cards: [Card] = []
func load() async {
let result = await fetchFromNetworkAsync()
await MainActor.run {
self.cards = result
}
}
}
5. 정리 및 팁
- SwiftUI에서 상태(@State, @Published)를 수정하는 코드는 항상 메인 스레드라고 생각하고 작성합니다.
- ViewModel 전체를 @MainActor로 선언하면 많은 부분이 단순해집니다.
@MainActor
final class SomeViewModel: ObservableObject { ... }
- “성능 때문에 백그라운드에서 업데이트해야 한다”는 발상은 대부분 오해에 가깝고,
진짜 무거운 연산은 값 계산까지만 백그라운드에서 수행하고, 최종 상태 반영은 메인에서 하는 구조가 좋습니다.
반응형
'Dev Study > SwiftUI' 카테고리의 다른 글
| SwiftUI에서 많이 하는 실수 - offset을 레이아웃 도구로 사용해서 반응형이 깨지는 실수 (0) | 2025.12.05 |
|---|---|
| SwiftUI에서 많이 하는 실수 - GeometryReader를 오용해 레이아웃이 엉망이 되는 실수 (0) | 2025.12.05 |
| SwiftUI에서 많이 하는 실수 - ViewModel에 비즈니스 로직과 UI 상태를 모두 섞어버리는 실수 (0) | 2025.12.05 |
| SwiftUI에서 많이 하는 실수 - ObservableObject에서 @Published를 남발하는 실수 (0) | 2025.12.05 |
| SwiftUI에서 많이 하는 실수 - .task와 .onAppear를 혼동해 비동기 로직이 중복 실행되는 실수 (0) | 2025.12.05 |
| SwiftUI에서 많이 하는 실수 - onAppear가 여러 번 호출되는 특성을 무시하고 API를 중복 호출하는 실수 (0) | 2025.12.05 |
| SwiftUI에서 많이 하는 실수 - sheet / fullScreenCover 상태를 여러 곳에서 제어해 모달이 깜빡이거나 중복되는 실수 (0) | 2025.12.05 |
| SwiftUI에서 많이 하는 실수 - NavigationStack / NavigationLink 상태 관리 실수로 화면 전환이 꼬이는 문제 (0) | 2025.12.05 |

