.task { } é um modificador no SwiftUI introduzido no iOS 15 que inicia uma operação assíncrona quando uma View aparece e a cancela automaticamente quando a View desaparece. Ao contrário do .onAppear, que executa código síncrono sem possibilidade de cancelamento, o .task trabalha com o contexto async/await e respeita o ciclo de vida da View: quando a View desaparece, o SwiftUI chama cancel() no Task criado. Isso evita vazamentos de memória e a execução de operações depois que a View não precisa mais ser atualizada. De acordo com a Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024), o .task é a forma preferida de carregar dados no SwiftUI porque funciona com segurança com Structured Concurrency e gerencia automaticamente o tempo de vida da operação assíncrona.
Pontos Principais
.task { } é um modificador de View que cria um Task em um contexto async quando a View aparece na tela. O SwiftUI executa o fechamento fornecido em uma thread de fundo, deixando a thread principal livre para operações de UI. Quando a View desaparece, o SwiftUI cancela automaticamente o Task através do mecanismo Structured Concurrency — isso garante que a operação assíncrona não continue executando depois que seu resultado não for mais necessário.
De acordo com Apple — Swift Programming Language (2025), o .task usa o conceito de Structured Concurrency introduzido no Swift 5.5. Cada .task cria uma tarefa filha dentro da tarefa da View pai. Se a tarefa pai for cancelada (a View desaparece), todas as tarefas filhas também são canceladas automaticamente. Isso simplifica radicalmente o gerenciamento do ciclo de vida de operações assíncronas em comparação com o armazenamento manual de referências a DispatchWorkItem ou AnyCancellable.
Ao contrário da abordagem tradicional com @State + chamadas manuais em .onAppear, o .task não requer armazenar uma referência ao Task para cancelamento posterior. O SwiftUI faz isso automaticamente, reduzindo código boilerplate e eliminando o risco de esquecer de cancelar uma tarefa.
struct ArticlesView: View {
@State var articles: [Article] = []
@State var error: Error?
var body: some View {
List(articles) { article in
Text(article.title)
}
.task {
do {
articles = await APIClient().fetchArticles()
} catch {
self.error = error
}
}
}
}
Muitos desenvolvedores estão acostumados a carregar dados em .onAppear, mas com a chegada de async/await e .task, esta abordagem se tornou obsoleta. .onAppear executa código de forma síncrona — para operações async dentro de .onAppear, é necessário envolver a chamada em Task { } e manter manualmente uma referência para possível cancelamento. .task faz isso automaticamente.
| Característica | .task { } | .onAppear |
|---|---|---|
| Contexto async | Async/await integrado | Requer wrapper Task { } |
| Auto-cancelamento | Sim, quando a View desaparece | Não, deve ser implementado manualmente |
| Structured Concurrency | Suporta | Não suporta |
| Re-execução | Apenas quando id muda | Toda vez que a View aparece |
| Recomendação Apple | Abordagem preferida | Para operações síncronas |
.onAppear ainda é útil para operações síncronas — por exemplo, registro ou configuração inicial de UI. Mas para carregamento assíncrono de dados, requisições de rede, operações com banco de dados ou sistema de arquivos, use .task. É mais seguro e limpo do ponto de vista arquitetural.
// ❌ Abordagem legada: Task em .onAppear sem cancelamento
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
.onAppear {
loadTask = Task { await loadData() }
}
.onDisappear { loadTask?.cancel() }
// ✅ Abordagem moderna: .task gerencia cancelamento
func body() { var body: some View { Text("") }
.task { await loadData() }
O modificador .task(id:) aceita um parâmetro adicional — um identificador. Quando o valor do identificador muda, o SwiftUI cancela a tarefa atual e inicia uma nova com o novo identificador. Isso é ideal para telas onde os dados dependem de um parâmetro selecionado — por exemplo, uma lista de artigos por categoria ou detalhes de um produto por ID.
struct CategoryView: View {
let categoryId: Int
@State var items: [Item] = []
var body: some View {
List(items) { item in
Text(item.name)
}
.task(id: categoryId) {
await loadItems(for: categoryId)
}
}
func loadItems(for id: Int) async {
do {
items = await APIClient().fetchItems(categoryId: id)
} catch {
// tratar erro
}
}
}
Quando categoryId muda, o SwiftUI cancela a requisição anterior e inicia uma nova. Isso é especialmente importante para mudanças rápidas de categoria — requisições antigas não competirão com as novas pela atualização do estado. Sem .task(id:), você teria que rastrear manualmente as mudanças através de .onChange e gerenciar o Task manualmente.
Embora .task cancele automaticamente a tarefa quando a View desaparece, a operação assíncrona em si deve verificar cooperativamente o cancelamento. O Swift usa um modelo de cancelamento cooperativo — Task.cancel() não interrompe a execução à força, mas apenas define a flag isCancelled. O código dentro da tarefa deve verificar periodicamente esta flag.
struct LoadingView: View {
@State var progress: Double = 0
var body: some View {
ProgressView(value: progress)
.task {
for i in 0..<100 {
// Verificar cancelamento
try Task.checkCancellation()
await Task.sleep(nanoseconds: 50_000_000)
progress = Double(i + 1) / 100.0
}
}
}
}
Task.checkCancellation() lança CancellationError se a tarefa foi cancelada. Esta é a forma mais simples de verificação — funciona em qualquer contexto async. Uma alternativa é verificar Task.isCancelled manualmente antes de operações custosas. Para URLSession, as requisições de rede são canceladas automaticamente quando a tarefa é cancelada, já que URLSession suporta Structured Concurrency nativamente.
.task é adequado para muitos cenários: desde carregamento simples de JSON até operações paralelas complexas com TaskGroup. Vamos ver três casos de uso típicos.
struct ProfileView: View {
@State var profile: Profile?
@State var isLoading = true
var body: some View {
Group {
if isLoading {
ProgressView()
} else if let profile {
Text(profile.name)
} else {
Text("Falha ao carregar")
}
}
.task {
defer { isLoading = false }
do {
profile = await APIClient().fetchProfile()
} catch {
// perfil permanece nil
}
}
}
}
struct DashboardView: View {
@State var stats: DashboardStats?
var body: some View {
Text("Painel")
.task {
stats = await Task {
await withThrowingTaskGroup { group in
group.addTask { await API().fetchUsers() }
group.addTask { await API().fetchOrders() }
group.addTask { await API().fetchRevenue() }
return DashboardStats(
users: try await group.next(),
orders: try await group.next(),
revenue: try await group.next()
)
}
}.value
}
}
}
O erro mais comum é mutar propriedades de UI dentro de .task sem alternar para a thread principal. Embora o SwiftUI retorne automaticamente as atualizações para a thread principal ao modificar @State em um contexto async, manipulações diretas de elementos UIKit dentro de .task podem causar um crash.
// ❌ Erro: sem tratamento de erros
.task {
let data = await fetchData() // falha ao ocorrer erro!
items = data
}
// ✅ Correto: do/catch
.task {
do {
items = await fetchData()
} catch {
errorMessage = error.localizedDescription
}
}
Perguntas Frequentes
.task cancela automaticamente a tarefa quando a View desaparece e suporta Structured Concurrency. Task { } em .onAppear requer manter manualmente uma referência à tarefa e chamar cancel() em .onDisappear. .task também é mais fácil de ler — ele indica explicitamente que o carregamento de dados faz parte do ciclo de vida da View.
Sim, para se inscrever em um AsyncSequence ou AsyncStream, use for await value in publisher.values dentro de .task. Isso funciona tanto com async/await quanto com Combine através da extensão Publisher.values. Quando a View desaparece, a iteração terminará automaticamente e a inscrição será cancelada.
Sim, se a View dentro de TabView for recriada em cada troca. A partir do iOS 18, o TabView pode manter Views na memória — nesse caso .task não reinicia. Use .task(id:) com um identificador de aba se precisar recarregar dados a cada troca de aba.
SwiftUI cancelará a tarefa quando a View desaparecer. Se a requisição URLSession estava dentro da tarefa, ela também será cancelada. Se a tarefa não suporta cancelamento cooperativo (por exemplo, não verifica isCancelled), ela continuará executando, mas seu resultado não será aplicado ao estado porque a View não existe mais.
Sim, você pode adicionar vários modificadores .task a uma única View. Cada um cria uma tarefa independente. Isso é útil para separar diferentes fontes de dados: um .task para carregar um perfil, outro para se inscrever em um WebSocket, um terceiro para monitorar geolocalização.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também