@MainActor — qué es, uso y características en código asíncrono de Swift

Autor: IT Sectr Publicado: 2026-03-19 Tiempo de lectura: 8 min

@MainActor es un actor global en el lenguaje Swift que garantiza la ejecución de código en el hilo principal. Según Apple Developer, 2024, @MainActor automatiza el cambio al hilo principal al trabajar con la UI, liberando al desarrollador de llamar manualmente a DispatchQueue.main.async. La anotación apareció en Swift 5.5 junto con el sistema async/await.

Puntos clave

  • @MainActor — un actor global de Swift para garantizar la ejecución en el hilo principal.
  • El sistema async/await — la base sobre la que se construye @MainActor.
  • Anotación de clase coloca automáticamente todos sus métodos en el hilo principal.
  • A diferencia de DispatchQueue.main, @MainActor verifica el hilo a nivel del compilador.
  • Actualizaciones de UI — el caso de uso principal de @MainActor en desarrollo iOS.

¿Qué es @MainActor?

@MainActor es un actor global en Swift que combina las propiedades de los actores con la garantía de ejecución en el hilo principal de la aplicación. Forma parte del sistema de concurrencia de Swift introducido en Swift 5.5 junto con async/await y la concurrencia estructurada. La anotación permite al desarrollador no preocuparse por el cambio manual de hilos y reduce la cantidad de errores de UI.

Definición y lugar en Swift Concurrency

Un actor en Swift es un tipo por referencia que aísla su estado y garantiza que solo un hilo pueda modificarlo. @MainActor es un actor global especial cuyo ejecutor es el hilo principal. Cualquier código marcado con @MainActor se ejecuta en el hilo principal, incluso si se llama desde una tarea en segundo plano.

Antes de @MainActor, los desarrolladores cambiaban manualmente al hilo principal mediante DispatchQueue.main.async. Esto era una fuente frecuente de errores: los desarrolladores olvidaban cambiar, lo que provocaba fallos debido a actualizaciones de la UI fuera del hilo principal. @MainActor resuelve este problema a nivel del sistema de tipos.

Razones para su creación

La fuente de la mayoría de los errores en aplicaciones iOS es la inseguridad de la UI — actualizar la interfaz desde un hilo secundario. Apple integró @MainActor en Swift Concurrency para hacer que el cambio al hilo principal sea automático y verificable por el compilador, eliminando toda una clase de errores en tiempo de ejecución.

¿Cómo funciona @MainActor?

El principio de funcionamiento de @MainActor se basa en el sistema de ejecución de Swift Concurrency. Cuando un hilo llama a una función marcada con @MainActor, el planificador la suspende en el ejecutor actual y la reanuda en el hilo principal. El compilador rastrea los límites de la llamada y garantiza la seguridad.

Ejecutor del hilo principal

La ejecución de @MainActor está gestionada por MainActor.shared — un ejecutor asociado con el hilo principal de la aplicación. Cuando una función asíncrona está marcada con @MainActor, siempre se reanuda en este ejecutor, independientemente del hilo en el que se inició la tarea original.

swift
import SwiftUI

class ViewModel: ObservableObject {
    @Published var items: [String] = []

    @MainActor
    func loadData() async {
        let result = await fetchRemoteData()
        items = result  // de forma segura, MainActor garantiza el hilo principal
    }
}

Herencia del contexto del actor

Si una función está marcada con @MainActor y llama a otra función asíncrona, hereda el contexto del actor por defecto. Esto significa que todas las llamadas anidadas también se ejecutan en el hilo principal, a menos que se especifique lo contrario. El compilador rastrea esto y emite un error al intentar pasar un cierre inconsistente.

@MainActor vs DispatchQueue.main

Comparar @MainActor y DispatchQueue.main ayuda a entender por qué el nuevo mecanismo se considera más seguro y conveniente, aunque ambos resuelven la misma tarea: ejecutar código en el hilo principal.

Seguridad a nivel de tipos

@MainActor es una verificación a nivel del compilador. Si intentas llamar a una función @MainActor desde un contexto inseguro, el compilador emitirá una advertencia o error. DispatchQueue.main.async es una llamada en tiempo de ejecución: el código se compilará pero puede fallar en tiempo de ejecución al intentar actualizar la UI desde un hilo secundario.

Rendimiento y sobrecarga

DispatchQueue.main.async agrega un bloque a la cola que puede ejecutarse con retraso. @MainActor con async/await realiza un cambio directo de ejecutor sin crear cierres innecesarios. Esto reduce la sobrecarga y hace que el tiempo de ejecución sea más predecible.

swift
// Enfoque antiguo
DispatchQueue.main.async {
    self.updateUI()
}

// Nuevo enfoque con @MainActor
@MainActor
func updateUI() {
    // se ejecuta en el hilo principal
    self.label.text = "Actualizado"
}
Criterio@MainActorDispatchQueue.main
Verificacióncompiladorruntime
Sintaxisanotación (declarativa)llamada (imperativa)
Sobrecargabaja (cambio de ejecutor)media (cierre + cola)
Capacidad de pruebaalta (MainActor.shared puede reemplazarse)baja (difícil de simular)

Uso de @MainActor en proyectos iOS

En proyectos iOS reales, @MainActor se utiliza en capas ViewModel, vistas SwiftUI y controladores UIKit. La anotación puede aplicarse tanto a métodos individuales como a todo el tipo.

Anotación de clase o estructura

Al marcar una clase con @MainActor, garantizas que todos sus métodos y propiedades solo son accesibles en el hilo principal. Esto es especialmente conveniente para vistas SwiftUI y clases ObservableObject: simplemente agregas @MainActor before class, y todas las propiedades @Published se actualizan de forma segura.

swift
@MainActor
final class UserListViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false

    func fetchUsers() async {
        isLoading = true
        users = await api.getUsers()
        isLoading = false
    }
}

Envolviendo código heredado

Al trabajar con código UIKit heredado donde el cambio de hilos era manual, puedes usar MainActor.run para el cambio explícito. Esto es conveniente para una transición incremental a Swift Concurrency sin reescribir toda la base de código.

swift
await MainActor.run {
    self.tableView.reloadData()
}

Limitaciones de @MainActor

A pesar de todas sus ventajas, @MainActor tiene una serie de limitaciones que es importante considerar al diseñar la arquitectura de la aplicación. Comprender los límites de aplicabilidad ayuda a evitar un uso incorrecto.

Rendimiento bajo uso intensivo

Si toda la cadena de llamadas está marcada con @MainActor, entonces cualquier trabajo pesado se realizará en el hilo principal, causando congelaciones de la UI. Se recomienda marcar solo la capa de UI con @MainActor, dejando la lógica de negocio y las solicitudes de red en actores secundarios o el ejecutor global.

Incompatibilidad con algunas APIs

Las APIs antiguas basadas en callbacks (por ejemplo, URLSession sin async/await) no soportan el contexto del actor. La integración requiere un envoltorio con CheckedContinuation. Además, @MainActor no es compatible con performSelector, target-action y otros patrones no asíncronos de UIKit.

Depuración multihilo

Al depurar aplicaciones con @MainActor, es más difícil reproducir condiciones de carrera porque el compilador previene muchas de ellas en tiempo de compilación en lugar de en tiempo de ejecución. Sin embargo, esto puede crear una falsa sensación de seguridad: el trabajo incorrecto con objetos mutables compartidos (por ejemplo, NSCache o variables globales compartidas) sigue siendo posible si no están marcados con @MainActor y se usan sin sincronización explícita.

Pruebas de @MainActor

@MainActor simplifica significativamente las pruebas de lógica de UI ya que elimina la necesidad de cambiar hilos manualmente en las pruebas. Sin embargo, hay particularidades que deben tenerse en cuenta al escribir pruebas unitarias y pruebas de UI.

Pruebas unitarias con MainActor

En XCTest, el entorno de prueba configura automáticamente el ejecutor del hilo principal. Cuando un método de prueba se ejecuta en el hilo principal, llamar a funciones @MainActor no requiere configuración adicional — se ejecutan en el mismo contexto. Para probar escenarios en segundo plano, usa MainActor.run dentro de una Task con prioridad y ejecutor explícitos, verificando por separado que el código funcione correctamente cuando se llama desde segundo plano.

Un enfoque común es probar ViewModel con @MainActor, donde se verifica que las propiedades @Published se actualicen correctamente después de operaciones asíncronas. Gracias a la herencia del contexto del actor, llamar a await dentro de la prueba garantiza la ejecución en el hilo principal sin garantías adicionales de DispatchQueue ni cambios manuales de contexto, lo que simplifica la escritura de pruebas.

Verificación de aislamiento durante la refactorización

Al refactorizar código existente a Swift Concurrency, verifica el aislamiento de @MainActor a través del compilador: cualquier llamada a métodos síncronos sin @MainActor desde un contexto @MainActor se marca como error. Esta propiedad se utiliza para migrar gradualmente un proyecto a async/await: marcas la capa ViewModel como @MainActor, y el compilador resalta todas las llamadas inseguras que deben moverse a actores secundarios.

Simulación y contexto de Actor

Al crear simulaciones para dependencias de @MainActor, usa protocolos con métodos async que declaran funciones asíncronas con tipos de retorno. Esto permite reemplazar servicios de red, bases de datos y otras dependencias externas sin romper el aislamiento del actor. El compilador verifica que la simulación implemente todos los requisitos de aislamiento, evitando el acceso accidental al código @MainActor desde hilos de prueba en segundo plano.

Espera para operaciones asíncronas

Al probar sincrónicamente código @MainActor, usa XCTestExpectation para esperar la finalización de operaciones asíncronas. Establece la expectativa en la prueba y ejecuta fulfillment dentro de un cierre que se ejecuta en el hilo principal. Si la prueba se cuelga indefinidamente — probablemente la llamada en el hilo principal no está ocurriendo, y debes verificar el aislamiento del actor. Para depurar el contexto de ejecución, es útil agregar una verificación Thread.isMainThread dentro del código de prueba.

Preguntas Frecuentes

¿Es necesario marcar toda la clase con @MainActor?

No, es suficiente marcar solo los métodos que actualizan la UI. Sin embargo, si una clase tiene varios métodos de este tipo, es más simple agregar @MainActor a toda la clase. Esto garantiza que todos sus miembros se ejecuten en el hilo principal y simplifica el mantenimiento del código.

¿En qué se diferencia @MainActor de @globalActor?

@MainActor es una instancia específica de un actor global vinculada al hilo principal. @globalActor es un protocolo para crear tus propios actores globales. Por ejemplo, puedes crear un @BackgroundActor para ejecutar código en un hilo secundario si la arquitectura del proyecto lo requiere.

¿Se puede usar @MainActor sin async/await?

Sí, las funciones síncronas con @MainActor también se ejecutan en el hilo principal. Sin embargo, el valor principal de @MainActor se revela con async/await, cuando una función asíncrona se reanuda automáticamente en el hilo principal sin cambio manual a través de DispatchQueue.main.

¿Cómo cancelar una tarea @MainActor?

Task.cancel() funciona con tareas @MainActor igual que con las normales. Una tarea @MainActor puede verificar Task.isCancelled o lanzar CancellationError. Al cancelar, el hilo principal no se bloquea — la tarea simplemente detiene su ejecución en el punto de suspensión más cercano.

¿Qué sucede si se llama a @MainActor desde un hilo secundario?

El compilador garantiza la seguridad: si llamas a una función @MainActor desde un contexto secundario, el compilador señalará el error. Para llamadas asíncronas, simplemente marca el código llamante con await, y el ejecutor cambiará al hilo principal. Para llamadas síncronas, se requiere un cambio explícito a través de MainActor.run.

Resumen

  • @MainActor — un actor global de Swift que garantiza la ejecución en el hilo principal.
  • Verificación del compilador elimina toda una clase de errores de seguridad de UI.
  • Anotación de clase completa coloca automáticamente todos sus métodos en el hilo principal.
  • MainActor.run — cambio explícito para código heredado y contextos síncronos.
  • A diferencia de DispatchQueue.main, @MainActor no crea cierres y usa cambio de ejecutor.
  • Cálculos pesados no deben realizarse bajo @MainActor para evitar congelaciones de UI.
  • Herencia del contexto del actor simplifica las cadenas de llamadas asíncronas y hace que el código sea consistente, predecible y seguro para la UI.

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