반응형

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 { ... }
  • “성능 때문에 백그라운드에서 업데이트해야 한다”는 발상은 대부분 오해에 가깝고,
    진짜 무거운 연산은 값 계산까지만 백그라운드에서 수행하고, 최종 상태 반영은 메인에서 하는 구조가 좋습니다.
반응형
Posted by 까칠코더
,