Uso de @MainActor

MainActor

@MainActor es un atributo en Swift que forma parte del sistema de concurrencia moderna introducido a partir de Swift 5.5. Este atributo asegura que el código que marca se ejecuta en el hilo principal, lo cual es esencial cuando necesitas interactuar con la interfaz de usuario (UI) u otros componentes que requieren que se ejecuten en el hilo principal para evitar problemas de concurrencia.

¿Qué es @MainActor?

@MainActor es un atributo que garantiza que las operaciones asociadas a él se ejecuten en el contexto del actor principal (el hilo principal de la aplicación). En aplicaciones basadas en interfaces gráficas, como las que desarrollas con SwiftUI o UIKit, todas las actualizaciones de UI deben ocurrir en el hilo principal. Intentar modificar la UI desde un hilo en segundo plano puede causar errores y comportamientos inesperados.

Características clave de @MainActor

Ejecución en el hilo principal: Las funciones, clases o propiedades que están anotadas con @MainActor siempre se ejecutan en el hilo principal.

Seguridad de concurrencia: Proporciona protección automática contra condiciones de carrera cuando se interactúa con la UI o cualquier otro recurso que deba estar vinculado al hilo principal.

Integración con Swift Concurrency: Funciona perfectamente con el modelo de concurrencia basado en async/await, asegurando que las tareas asíncronas que marcan como @MainActor se ejecuten en el hilo principal.

Uso Práctico de @MainActor

A continuación, veamos varios ejemplos de cómo usar @MainActor en código real.

1. Marcar un método que interactúa con la UI

Cuando se realiza una operación en segundo plano, como la descarga de datos o un cálculo intensivo, y necesitas actualizar la interfaz de usuario después, @MainActor asegura que esta actualización se realice en el hilo correcto.

				
					import SwiftUI

struct ContentView: View {
    @State private var message: String = "Cargando..."
    
    var body: some View {
        VStack {
            Text(message)
                .padding()
            
            Button("Descargar Datos") {
                Task {
                    await fetchData()
                }
            }
        }
    }

    // Método asíncrono que descarga datos
    func fetchData() async {
        sleep(2) // Simular una operación de descarga en segundo plano
        let data = "Datos descargados"
        
        // Actualizamos la UI, asegurándonos que sea en el hilo principal con @MainActor
        await updateUI(with: data)
    }

    // Este método está en el MainActor y se asegura que se ejecute en el hilo principal
    @MainActor
    func updateUI(with data: String) {
        message = data // Esto modifica el estado de la UI, por lo que debe estar en el hilo principal
    }
}
				
			

Explicación del código:

fetchData: Este método simula una operación en segundo plano, como descargar datos. Después de obtener los datos, necesitamos actualizar el estado de la UI.

@MainActor en updateUI(with:): Este método actualiza la variable message, que está vinculada a un Text. Dado que esta es una operación que afecta la UI, debe ejecutarse en el hilo principal. Marcamos el método con @MainActor para asegurarnos de que la actualización ocurra correctamente en el hilo principal.

2. Marcar una clase completa como @MainActor

En algunos casos, puedes tener clases enteras dedicadas a manejar la interfaz de usuario o recursos que dependen del hilo principal. En vez de marcar cada método individualmente, puedes anotar toda la clase con @MainActor, garantizando que todos sus métodos y propiedades se ejecuten en el hilo principal.

				
					import Foundation

@MainActor
class DataManager {
    var data: [String] = []
    
    // Simula la descarga de datos
    func fetchData() async {
        // Simular la descarga en segundo plano
        try? await Task.sleep(nanoseconds: 2_000_000_000)
        let newData = ["Item 1", "Item 2", "Item 3"]
        
        // Modificar la propiedad en el hilo principal
        data.append(contentsOf: newData)
    }
    
    // Este método también estará en el MainActor
    func clearData() {
        data.removeAll()
    }
}
				
			

Explicación del código:

Clase DataManager: Toda la clase está anotada con @MainActor, lo que asegura que todas las modificaciones de su propiedad data ocurran en el hilo principal.

Modificación de la UI o estados dependientes del hilo principal: La propiedad data puede estar vinculada a una vista de tabla o lista en la UI, y su modificación debe ser segura en cuanto a concurrencia.

3. Usar @MainActor con SwiftUI

En aplicaciones SwiftUI, muchos cambios en el estado de las vistas deben realizarse en el hilo principal. Aquí es donde @MainActor es útil para garantizar que cualquier cambio que afecte la UI suceda en el hilo correcto.

				
					import SwiftUI

struct AsyncImageView: View {
    @State private var image: UIImage? = nil
    
    var body: some View {
        VStack {
            if let image = image {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200, height: 200)
            } else {
                ProgressView("Cargando imagen...")
            }
        }
        .onAppear {
            Task {
                await downloadImage()
            }
        }
    }
    
    // Simulación de descarga de imagen
    func downloadImage() async {
        let url = URL(string: "https://ejemplo.com/imagen.jpg")!
        let (data, _) = try! await URLSession.shared.data(from: url)
        let downloadedImage = UIImage(data: data)!
        
        // Actualizar la UI usando @MainActor
        await updateUI(with: downloadedImage)
    }
    
    // Aseguramos que esta función se ejecute en el hilo principal
    @MainActor
    func updateUI(with newImage: UIImage) {
        self.image = newImage
    }
}
				
			

Explicación del código:

Descarga de imagen: El método downloadImage descarga la imagen en un hilo de fondo utilizando URLSession.

Actualización de la UI: Dado que estamos actualizando el estado de la vista (self.image), es crucial que esto ocurra en el hilo principal. @MainActor asegura que este cambio se realice de manera segura en el contexto adecuado.

Cuándo Usar @MainActor

@MainActor es útil en los siguientes casos:

1. Actualizaciones de la UI: Todas las modificaciones relacionadas con la UI, como actualizar vistas, controles o datos visualizados, deben ejecutarse en el hilo principal.

2. Modificación de estados compartidos en el hilo principal: Si tienes estados o datos que deben ser coherentes con la ejecución en el hilo principal, como los estados de vista en SwiftUI o UIKit.

3. Operaciones largas en segundo plano: Después de realizar operaciones asíncronas en segundo plano, @MainActor te ayuda a garantizar que la sincronización con la UI se realice correctamente.

Conclusión

@MainActor es una herramienta poderosa para asegurar la seguridad en la concurrencia cuando trabajas con la UI o cualquier operación que deba ejecutarse en el hilo principal. Usarlo correctamente evita condiciones de carrera y asegura que la interfaz de usuario se mantenga reactiva y estable, especialmente en aplicaciones asíncronas donde las tareas en segundo plano son comunes.

Si desea conocer más sobre @MainActor puedes visitar estos enlaces:

Si le ha gustado el artículo puedes dejarme un comentario. Feliz codificación!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio