개발/SwiftUI
SwiftUI에서 많이 하는 실수 - .task와 .onAppear를 혼동해 비동기 로직이 중복 실행되는 실수
까칠코더
2025. 12. 5. 11:27
반응형
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 핸들을 직접 관리하는 쪽으로 확장할 수도 있습니다.
반응형