반응형

SwiftUI에서 많이 하는 실수 - onAppear가 여러 번 호출되는 특성을 무시하고 API를 중복 호출하는 실수

 

SwiftUI에서 onAppear는 View가 화면에 “보일 때마다” 호출됩니다.
View가 재생성되거나, 레이아웃/상태 변화로 인해 다시 계산될 때도 호출될 수 있습니다.
이 사실을 고려하지 않고 onAppear 안에서 네트워크 호출을 직접 하면,
같은 API를 여러 번 호출하는 문제가 생깁니다.


1. 문제 원인

  • UIKit의 viewDidLoad / viewWillAppear와 동일하게 생각
  • “화면 진입 시 1회 호출”이라고 오해
  • ViewModel에 플래그를 두지 않고 매번 load() 호출

2. 나타나는 증상

  • 탭을 바꿨다 돌아올 때마다 동일 데이터 재호출
  • 스크롤/필터 변경 등 단순 동작에 의해 API가 반복 호출
  • 서버 로그에서 같은 요청이 여러 번 찍히는 것을 발견

3. 잘못된 코드 예시

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

    var body: some View {
        List(viewModel.cards) { card in
            Text(card.title)
        }
        .onAppear {
            // ❌ 진입할 때마다 (또는 재 계산마다) 호출될 수 있음
            viewModel.load()
        }
    }
}

4. 올바른 코드 예시 (ViewModel에서 1회 로딩 보장)

final class CardsViewModel: ObservableObject {
    @Published var cards: [Card] = []
    private var hasLoaded = false

    func loadIfNeeded() {
        guard !hasLoaded else { return }
        hasLoaded = true
        // 네트워크 / 디스크 로딩
    }
}

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

    var body: some View {
        List(viewModel.cards) { card in
            Text(card.title)
        }
        .onAppear {
            viewModel.loadIfNeeded()   // ✅ 중복 호출 방지
        }
    }
}

또는 Swift Concurrency를 쓰는 경우:

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

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

5. 정리 및 팁

  • “정말 한 번만 실행되어야 하는 초기화 로직”은 View가 아니라 ViewModel 내부에서 플래그로 제어하는 것이 안전합니다.
  • onAppear는 View가 여러 번 등장·소멸할 수 있는 환경이라는 점을 항상 기억해야 합니다.
반응형
Posted by 까칠코더
,