.task { } — qué es, modificador async y carga de datos en View

Autor: IT Sectr Publicado: 2026-06-26 Tiempo de lectura: 9 min

.task { } es un modificador de SwiftUI introducido en iOS 15 que inicia una operación asíncrona cuando aparece una View y la cancela automáticamente cuando desaparece. A diferencia de .onAppear, que ejecuta código síncrono sin posibilidad de cancelación, .task funciona con el contexto async/await y respeta el ciclo de vida de la View: cuando la View desaparece, SwiftUI llama a cancel() en el Task creado. Esto evita fugas de memoria y la ejecución de operaciones después de que la View ya no necesita actualizarse. Según Apple WWDC Session 10132 — Meet async/await in SwiftUI (2024), .task es la forma preferida de cargar datos en SwiftUI porque trabaja de forma segura con Structured Concurrency y gestiona automáticamente el tiempo de vida de la operación asíncrona.

Puntos Clave

  • .task { } — modificador de SwiftUI para carga asíncrona de datos al aparecer una View, disponible desde iOS 15.
  • Cancelación automática — al desaparecer la View, SwiftUI cancela el Task, evitando fugas de memoria.
  • Contexto async/await — dentro de .task están disponibles llamadas async sin necesidad de DispatchQueue o Combine.
  • .task(id:) — variante con identificador que reinicia la tarea al cambiar el valor especificado.
  • Structured Concurrency — .task soporta Structured Concurrency y TaskGroup para operaciones paralelas.

Qué es .task { } en SwiftUI

.task { } es un modificador de View que crea un Task en un contexto async cuando la View aparece en pantalla. SwiftUI ejecuta el cierre proporcionado en un hilo secundario, dejando el hilo principal libre para operaciones de UI. Cuando la View desaparece, SwiftUI cancela automáticamente el Task mediante el mecanismo de Structured Concurrency — esto garantiza que la operación asíncrona no continúe ejecutándose después de que su resultado ya no sea necesario.

Según Apple — Swift Programming Language (2025), .task utiliza el concepto de Structured Concurrency introducido en Swift 5.5. Cada .task crea una tarea hija dentro de la tarea de la View padre. Si la tarea padre se cancela (la View desaparece), todas las tareas hijas también se cancelan automáticamente. Esto simplifica radicalmente la gestión del ciclo de vida de las operaciones asíncronas en comparación con almacenar manualmente referencias a DispatchWorkItem o AnyCancellable.

A diferencia del enfoque tradicional con @State + llamadas manuales en .onAppear, .task no requiere almacenar una referencia al Task para su posterior cancelación. SwiftUI lo hace automáticamente, reduciendo el código repetitivo y eliminando el riesgo de olvidar cancelar una tarea.

swift
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
            }
        }
    }
}

.task vs .onAppear: diferencias clave

Muchos desarrolladores están acostumbrados a cargar datos en .onAppear, pero con la llegada de async/await y .task, este enfoque ha quedado obsoleto. .onAppear ejecuta código de forma síncrona — para operaciones async dentro de .onAppear, es necesario envolver la llamada en Task { } y mantener manualmente una referencia para posible cancelación. .task hace esto automáticamente.

Característica.task { }.onAppear
Contexto asyncAsync/await integradoRequiere envoltorio Task { }
Auto-cancelaciónSí, al desaparecer la ViewNo, debe implementarse manualmente
Structured ConcurrencySoportaNo soporta
Re-ejecuciónSolo cuando cambia idCada vez que aparece la View
Recomendación AppleEnfoque preferidoPara operaciones síncronas

.onAppear sigue siendo útil para operaciones síncronas — por ejemplo, registro o configuración inicial de UI. Pero para carga asíncrona de datos, solicitudes de red, operaciones con base de datos o sistema de archivos, usa .task. Es más seguro y limpio desde el punto de vista arquitectónico.

swift
// ❌ Enfoque antiguo: Task en .onAppear sin cancelación
var loadTask: Task<Void, Never>?
func body() { var body: some View { Text("") }
    .onAppear {
        loadTask = Task { await loadData() }
    }
    .onDisappear { loadTask?.cancel() }

// ✅ Enfoque moderno: .task gestiona la cancelación
func body() { var body: some View { Text("") }
    .task { await loadData() }

.task(id:) — reinicio al cambiar datos

El modificador .task(id:) acepta un parámetro adicional — un identificador. Cuando el valor del identificador cambia, SwiftUI cancela la tarea actual e inicia una nueva con el nuevo identificador. Esto es ideal para pantallas donde los datos dependen de un parámetro seleccionado — por ejemplo, una lista de artículos por categoría o detalles de un producto por ID.

swift
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 {
            // manejar error
        }
    }
}

Cuando categoryId cambia, SwiftUI cancela la solicitud anterior e inicia una nueva. Esto es especialmente importante para cambios rápidos de categoría — las solicitudes antiguas no competirán con las nuevas por la actualización del estado. Sin .task(id:), tendrías que rastrear manualmente los cambios mediante .onChange y gestionar el Task manualmente.

Cancelación de tareas y verificación isCancelled

Aunque .task cancela automáticamente la tarea cuando la View desaparece, la operación asíncrona en sí misma debe verificar cooperativamente la cancelación. Swift usa un modelo de cancelación cooperativa — Task.cancel() no detiene la ejecución forzosamente, sino que solo establece el flag isCancelled. El código dentro de la tarea debe verificar periódicamente este flag.

swift
struct LoadingView: View {
    @State var progress: Double = 0
    
    var body: some View {
        ProgressView(value: progress)
            .task {
                for i in 0..<100 {
                    // Verificar cancelación
                    try Task.checkCancellation()
                    
                    await Task.sleep(nanoseconds: 50_000_000)
                    progress = Double(i + 1) / 100.0
                }
            }
    }
}

Task.checkCancellation() lanza CancellationError si la tarea fue cancelada. Esta es la forma más simple de verificación — funciona en cualquier contexto async. Una alternativa es verificar Task.isCancelled manualmente antes de operaciones costosas. Para URLSession, las solicitudes de red se cancelan automáticamente cuando se cancela la tarea, ya que URLSession soporta Structured Concurrency de forma nativa.

Ejemplos prácticos de .task

.task es adecuado para muchos escenarios: desde carga simple de JSON hasta operaciones paralelas complejas con TaskGroup. Veamos tres casos de uso típicos.

Carga con manejo de errores

swift
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("Error al cargar")
            }
        }
        .task {
            defer { isLoading = false }
            do {
                profile = await APIClient().fetchProfile()
            } catch {
                // perfil sigue siendo nil
            }
        }
    }
}

Carga paralela con TaskGroup

swift
struct DashboardView: View {
    @State var stats: DashboardStats?
    
    var body: some View {
        Text("Panel")
            .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
            }
    }
}

Errores comunes con .task

El error más común es mutar propiedades de UI dentro de .task sin cambiar al hilo principal. Aunque SwiftUI devuelve automáticamente las actualizaciones al hilo principal al modificar @State en un contexto async, las manipulaciones directas de elementos UIKit dentro de .task pueden causar un crash.

  • Olvidar try/catch — .task no maneja errores automáticamente. Todas las funciones que lanzan errores deben estar envueltas en do/catch, de lo contrario la aplicación fallará.
  • Condición de carrera — si varios .task(id:) se inician con diferentes ids y actualizan el mismo estado, pueden ocurrir condiciones de carrera. Usa propiedades separadas para diferentes fuentes de datos.
  • Operaciones síncronas largas — .task no vuelve asíncrono el código síncrono. Si hay trabajo síncrono pesado dentro de .task, envuélvelo en Task.detached o muévelo a un método async separado.
  • Ignorar CancellationError — al verificar Task.checkCancellation(), el CancellationError debe propagarse hacia arriba, no suprimirse. Suprimir la cancelación puede provocar fugas de memoria.
swift
// ❌ Error: sin manejo de errores
.task {
    let data = await fetchData() // falla al producirse un error!
    items = data
}

// ✅ Correcto: do/catch
.task {
    do {
        items = await fetchData()
    } catch {
        errorMessage = error.localizedDescription
    }
}

Preguntas Frecuentes

¿Cuál es la diferencia entre .task y usar Task { } en .onAppear?

.task cancela automáticamente la tarea cuando la View desaparece y soporta Structured Concurrency. Task { } en .onAppear requiere mantener manualmente una referencia a la tarea y llamar a cancel() en .onDisappear. .task también es más fácil de leer — indica explícitamente que la carga de datos es parte del ciclo de vida de la View.

¿Se puede usar .task para suscribirse a un publisher?

Sí, para suscribirse a un AsyncSequence o AsyncStream, usa for await value in publisher.values dentro de .task. Esto funciona tanto con async/await como con Combine a través de la extensión Publisher.values. Cuando la View desaparece, la iteración terminará automáticamente y la suscripción se cancelará.

¿Cómo funciona .task con TabView — se reinicia la tarea al cambiar de pestaña?

Sí, si la View dentro de TabView se recrea en cada cambio. A partir de iOS 18, TabView puede mantener las Views en memoria — en ese caso .task no se reinicia. Usa .task(id:) con un identificador de pestaña si necesitas recargar datos en cada cambio de pestaña.

¿Qué sucede si una View con .task desaparece antes de que se complete la solicitud?

SwiftUI cancelará la tarea cuando la View desaparezca. Si la solicitud URLSession estaba dentro de la tarea, también se cancelará. Si la tarea no soporta cancelación cooperativa (por ejemplo, no verifica isCancelled), continuará ejecutándose, pero su resultado no se aplicará al estado porque la View ya no existe.

¿Se pueden usar múltiples modificadores .task en una sola View?

Sí, puedes agregar varios modificadores .task a una sola View. Cada uno crea una tarea independiente. Esto es útil para separar diferentes fuentes de datos: un .task para cargar un perfil, otro para suscribirse a un WebSocket, un tercero para monitorear la geolocalización.

Resumen

  • .task { } — modificador de SwiftUI para operaciones async con auto-cancelación al desaparecer la View.
  • Soporte async/await — dentro de .task hay un contexto async completo sin necesidad de envoltorio Task.
  • .task(id:) — reinicia la tarea al cambiar el identificador, reemplazando .onChange manual.
  • Cancelación cooperativa — usa Task.checkCancellation() para verificar la cancelación dentro de la tarea.
  • Structured Concurrency — .task soporta TaskGroup y operaciones paralelas con cancelación de tareas hijas.
  • Reemplazo de .onAppear — para carga asíncrona de datos, usa .task en lugar del patrón .onAppear + Task + .onDisappear.
  • Manejo de errores — todas las llamadas async dentro de .task deben estar envueltas en do/catch para evitar crashes.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también