¿Cuál es la mejor solución para tareas en segundo plano utilizando Swift async, await, @MainActor?
Estoy estudiando async
, await
, @MainActor
de Swift.
Quiero ejecutar un proceso largo y mostrar el progreso.
import SwiftUI
@MainActor
final class ViewModel: ObservableObject {
@Published var count = 0
func countUpAsync() async {
print("countUpAsync() isMain=\(Thread.isMainThread)")
for _ in 0..<5 {
count += 1
Thread.sleep(forTimeInterval: 0.5)
}
}
func countUp() {
print("countUp() isMain=\(Thread.isMainThread)")
for _ in 0..<5 {
self.count += 1
Thread.sleep(forTimeInterval: 0.5)
}
}
}
struct ContentView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
VStack {
Text("Count=\(viewModel.count)")
.font(.title)
Button("Start Dispatch") {
DispatchQueue.global().async {
viewModel.countUp()
}
}
.padding()
Button("Start Task") {
Task {
await viewModel.countUpAsync()
}
}
.padding()
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Cuando presiono el botón “Start Dispatch”, el “Count” se actualiza pero recibo una advertencia:
No está permitido publicar cambios desde hilos en segundo plano; asegúrese de publicar valores desde el hilo principal (a través de operadores como
receive(on:)
) en las actualizaciones del modelo.
Pensé que la clase ViewModel
es @MainActor
, que la propiedad count
se manipula en el hilo Main
, pero no es así.
¿Debo utilizar DispatchQueue.main.async{}
para actualizar count
aunque tenga @MainActor
?
Cuando presiono el botón “Start Task”, el botón se mantiene presionado hasta que countUpAsync()
se complete y no se actualiza el contador en la pantalla.
¿Cuál es la mejor solución?
davy.ai
Al trabajar con SwiftUI y concurrencia, es importante asegurarse de que cualquier actualización de la interfaz de usuario se realice en el hilo principal. Si bien la clase
ViewModel
está anotada con el atributo@MainActor
, el problema de actualizar la propiedadcount
desde un hilo en segundo plano surge del hecho de que la instancia deViewModel
no se está ejecutando en el hilo principal.Para actualizar la propiedad
count
en el hilo principal, puedes utilizarDispatchQueue.main.async{}
o la funciónawait
con el atributo@MainActor
. Una solución para resolver la advertencia sería utilizarDispatchQueue.main.async{}
para actualizarcount
en la funcióncountUp()
de la siguiente manera:De manera similar, para el botón “Iniciar tarea” que actualiza
count
de forma asíncrona, puedes utilizar la funciónawait
con el atributo@MainActor
de la siguiente manera:Al utilizar
MainActor.run
con la funciónawait
, se otorga permiso a la ejecución decountUpAsync()
en un hilo en segundo plano para publicar actualizaciones de vuelta al hilo principal, lo que permite actualizar la interfaz de usuario en consecuencia.En general, es importante recordar que al tratar la concurrencia en SwiftUI, cualquier actualización de la interfaz de usuario debe realizarse en el hilo principal, incluso si la clase en sí misma ha sido marcada con el atributo
@MainActor
.