Swift - .task

Marble·2026년 3월 5일

Concurrency

목록 보기
7/10

이번 글에서는 .task에 대해 알아볼게요

.task

.task는 SwiftUI iOS 15+에서 추가된 뷰 수식어로, SwiftUI View lifecycle에 연결된 비동기 작업 modifier에요

struct ContentView: View {
    @State private var title = ""

    var body: some View {
        Text(title)
            .task {
                title = await fetchTitle()
            }
    }
}

Task와의 차이점

1.자동취소

Task는 SwiftUI lifecycle과 연결되지 않습니다. 그래서 취소를 하려면 직접 cancel함수를 호출해야하며, 취소하지 않을 경우 View가 사라져도 계속 실행될 수 있죠. 반면 .task는 SwiftUI lifecycle과 연결되어 있어서 View가 사라지면 자동으로 취소가 돼요. 그래서 onAppear + Task { } 조합을 자주 썼다면, .task가 그 역할을 더 안전하게 대신해줘요

예를 들어 페이지 이동이 빈번한 앱을 생각해봤을때, 유저가 목록 화면에서 상세 화면으로 들어갔다가 바로 뒤로 나올 수 있죠

struct ArticleListView: View {
    @State private var articles: [Article] = []

    var body: some View {
        List(articles) { article in
            Text(article.title)
        }
        .task {
            // 뷰가 사라지면 이 네트워크 요청은 자동 취소됨
            articles = await articleRepository.fetchAll()
        }
    }
}

유저가 뷰를 나가면 진행 중이던 네트워크 요청이 취소된다. 불필요한 리소스 낭비를 막고, 이미 사라진 뷰에 상태를 업데이트하려는 시도도 발생하지 않아요. 단, 작업이 Task.isCancelled나 취소에 응답하는 API(URLSession, Task.sleep 등)를 사용해야 실제로 중단되기 때문에 취소 신호를 무시하는 코드라면 내부 작업은 계속 실행돼요

2.id파라미터

.task(id: searchText) {
    await search()
}

id 값이 변경되면 다음 흐름처럼 기존 실행 중인 작업을 취소하고 새로운 값으로 다시 실행해요

searchText 변경
↓
SwiftUI View 다시 계산
↓
.task(id: searchText) 감지
↓
기존 Task cancel
↓
새 Task 생성
↓
search() 실행

반면 Task는 그렇지 않죠. 이는 Task는 그냥 비동기 작업 생성일 뿐 SwiftUI state 시스템과 연결되지 않기 때문이에요

.onAppear {
    Task {
        await search()
    }
}
searchText 변경
↓
View 다시 렌더링
↓
Task 영향 없음

만약 Task를 id 기반으로 실행되게 하려면 다음과 같이 할 수 있어요

.onChange(of: searchText) { _ in
    Task {
        await search()
    }
}

다만 이때는 기존 Task에 대한 cancel이 안 되기 때문에 여러 Task가 동시에 API 호출하는 경우가 발생할 수도 있어요. 둘의 차이점을 표로정리하면 다음과 같아요

onAppear + Task.task
실행 시점뷰 등장 시뷰 등장 시
취소 시점수동 관리 필요뷰 소멸 시 자동 취소
취소 전파Task 저장 후 직접 cancel()자동
id기반 작업onChange를 통해 가능(단, 중복 호출 가능성 있음)파라미터에 id만 전달하면 됨
코드 간결성보통높음

특징

우선순위 지정 가능

.task는 Task처럼 우선순위를 지정할 수 있어요. 이때 기본값은 .userInitiated에요

.task(priority: .background) {
    await prefetchNextPageData()
}

@MainActor와의 관계

.task 클로저는 기본적으로 뷰가 실행되는 컨텍스트, 즉 @MainActor에서 시작해요. 따라서 클로저 안에서 @State 같은 UI 상태를 별도의 await MainActor.run 없이 바로 수정할 수 있죠

.task {
    let data = await fetchData()  // 백그라운드에서 실행 가능
    self.items = data              // @MainActor 컨텍스트에서 안전하게 업데이트
}

마지막으로 언제 .task를 쓰고 언제 Task를 쓰면 좋을지 표로 정리할게요

상황추천
뷰가 나타날 때 데이터 로드.task
특정 값 변경 시 재요청.task(id:)
버튼 탭 등 이벤트 기반 작업Task { } (in action handler)
뷰 생명주기와 무관한 백그라운드 작업Task.detached

.task는 뷰에 종속된 작업에 최적화되어 있기 때문에, 뷰와 관계없는 작업이라면 Task나 Task.detached가 더 적합해요

profile
개발자가 되고 싶은 공돌이

0개의 댓글