반응형

SwiftUI에서 많이 하는 실수 - .task와 .onAppear를 혼동해 비동기 로직이 중복 실행되는 실수

 

Swift 5.5 이후 .task modifier는 async 작업을 View 생명주기와 연결해주는 전용 훅입니다.
기존의 .onAppear { Task { ... } } 방식과 섞어 쓰면,
동일 비동기 로직이 두 번 실행되는 경우가 자주 발생합니다.


1. 문제 원인

  • 기존에 쓰던 onAppear 위에 task를 추가
  • “혹시 안 불릴까 봐” 두 군데에 같은 호출을 넣음
  • task의 취소/재시작 타이밍을 명확히 이해하지 못함

2. 나타나는 증상

  • 화면 진입 시 같은 API가 2번 호출됨
  • 빠르게 탭 전환 등으로 화면을 들어갔다 나가면 여러 개의 Task가 동시에 살아 있음
  • 로그를 찍어보면 의도보다 2배 많은 출력

3. 잘못된 코드 예시

struct CardsView: View {
    @StateObject private var viewModel = CardsViewModel()

    var body: some View {
        List(viewModel.cards) { card in
            Text(card.title)
        }
        .onAppear {
            Task {
                await viewModel.load()
            }
        }
        .task {
            await viewModel.load()
        }
    }
}

4. 올바른 코드 예시

4-1. 비동기 초기화를 task로 통일

struct CardsView: View {
    @StateObject private var viewModel = CardsViewModel()

    var body: some View {
        List(viewModel.cards) { card in
            Text(card.title)
        }
        .task {
            await viewModel.loadIfNeeded()
        }
    }
}

extension CardsViewModel {
    func loadIfNeeded() async {
        guard !hasLoaded else { return }
        hasLoaded = true
        await load()
    }
}

4-2. onAppear는 가벼운 동기 작업에만 사용

struct SomeView: View {
    @State private var appearedAt: Date?

    var body: some View {
        Text("Hello")
            .onAppear {
                appearedAt = .now       // 가벼운 동기 로직
            }
            .task {
                await doAsyncWork()     // 비동기 로직
            }
    }
}

5. 정리 및 팁

  • async/await 기반 초기화는 .task 한 곳에만 두는 것을 기본 규칙으로 삼는 것이 좋습니다.
  • onAppear는 최대한 UI 플래그/로컬 상태 업데이트 같은 가벼운 작업으로 제한합니다.
  • Task 취소 타이밍이 중요하다면 task(id:)나 Task 핸들을 직접 관리하는 쪽으로 확장할 수도 있습니다.
반응형
Posted by 까칠코더
,