DEV Community

GoyesDev
GoyesDev

Posted on

[SC] ¿Cuándo y cómo usar @MainActor?

Preguntas

¿Qué es @MainActor y de qué protocolo hereda?

MainActor es un actor global que ejecuta el codigo en el hilo principal. Conforma el protocolo GlobalActor.

¿En qué elementos del código se puede aplicar el atributo @MainActor?

Se puede aplicar @MainActor a variables globales, atributos, metodos, tipos de datos (como clases) y closures.

¿Cuál es la diferencia entre usar @MainActor como atributo y usar MainActor.run {}?

Usar @MainActor me obliga a ejecutar un metodo de forma asincrona (con async/await) mientras que llamar un codigo con MainActor.run {} implica que el desarrollador recuerde envolver la invocacion con el.

En el primer caso, el compilador saca un error si no se pone await. En el segundo, alguien podria olvidar envolver con MainActor.run {} y provocaria un error en ejecucion.

¿Por qué un método marcado con @MainActor no garantiza ejecución en el hilo principal si se llama desde DispatchQueue.global()?

Segun el articulo, si se llama el metodo marcado con @MainActor desde un DispatchQueue.global() sin usar await, entonces no se puede garantizar la ejecucion del codigo en el hilo principal.

¿Cuándo es apropiado usar MainActor.assumeIsolated y cuáles son sus riesgos?

Puede ser que yo este 100% seguro de que estoy parado en un metodo que se esta ejecutando en el hilo principal. En este caso, no es necesario hacer un llamado asincrono a await MainActor.run { } sino que se puede hacer un llamado sincrono a MainActor.assumeIsolated. Si el metodo invocador en realidad no estaba en el hilo principal, entonces la aplicacion se explota.

Por ejemplo, supongamos que tenemos un metodo que corre en el actor principal:

@MainActor
func metodoAisladoEnMainActor() {
  // Código que necesita ejecutarse en el hilo principal...
}
Enter fullscreen mode Exit fullscreen mode

Tambien supongamos que hay un metodo A, que invoca a un metodo B, usando DispatchQueue.main.async:

func methodA() {
  DispatchQueue.main.async {
    methodB()
  }
}

func methodB() {

}
Enter fullscreen mode Exit fullscreen mode

Debido al flujo de control que podemos ver al escribir el codigo, el humano sabe que el metodo B corre tambien en el hilo principal. Por tanto, no es necesario hacer un llamado asincrono al MainActor. En su lugar, se puede invocar MainActor.assumeIsolated.

func methodB() {
  MainActor.assumeIsolated {
    metodoAisladoEnMainActor()
  }
}
Enter fullscreen mode Exit fullscreen mode

El codigo anterior funciona correctamente, sin embargo, requiere que methodB si o si se haya ejecutado en el hilo principal.

¿Qué restricciones existen al anotar una clase con un actor global?

Se puede anotar una clase con un actor global si cumple con cualquiera de estas condiciones:

  1. No tiene super clase,
  2. Su super clase tambien esta anotada con el mismo GlobalActor (en este caso, MainActor)
  3. La super clase es NSObject.

¿Cómo reemplaza @MainActor a los bloques DispatchQueue.main.async del código tradicional?

  • @MainActor solo despacha al hilo principal cuando es necesario, evitando despachos redundantes cuando ya se esta en el hilo principal.
  • El compilador garantiza el aislamiento, eliminando el riesgo de olvidar el llamado a DispatchQueue.main.async.
  • Se eliminan bloques DispatchQueue.main.async dispersos, reduciendo el desorden.

COnsiderar el siguiente codigo:

func fetchImage(for url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) {
  URLSession.shared.dataTask(with: url) { data, response, error in
    guard let data, let image = UIImage(data: data) else {
      DispatchQueue.main.async {
        completion(.failure(ImageFetchingError.imageDecodingFailed))
      }
      return
    }

    DispatchQueue.main.async {
      completion(.success(image))
    }
  }.resume()
}
Enter fullscreen mode Exit fullscreen mode

El codigo anterior se puede reemplazar asi:

@MainActor
func fetchImage(for url: URL) async throws -> UIImage {
  let (data, _) = try await URLSession.shared.data(from: url)
  guard let image = UIImage(data: data) else {
    throw ImageFetchingError.imageDecodingFailed
  }
  return image
}
Enter fullscreen mode Exit fullscreen mode

¿Qué ocurre si se llama a assumeIsolated desde un contexto donde no se está en el ejecutor correcto?

Si se llama un codigo con assumeIsolated y no se esta usando el mismo ejecutor, entonces la aplicacion se explota. Tener en cuenta que dos actores pueden compartir el mismo ejecutor.

¿Por qué el compilador puede detectar acceso al hilo principal en algunos casos pero no en todos?

El compilador analiza estaticamente todos los metodos. No puede asumir simplemente que un metodo esta corriendo en el hilo principal. Para esto se tiene que usar assumeIsolated.

Recordar

¿Puedes explicar con tus propias palabras la diferencia entre aislamiento en tiempo de compilación y la aserción en tiempo de ejecución de assumeIsolated?

¿Cómo se vería un ViewModel correctamente aislado al hilo principal usando @MainActor?

Revisión

¿Cuál es la recomendación principal del autor sobre cuándo y cómo usar @MainActor?

¿Qué buena práctica adicional sugiere el autor cuando se usa assumeIsolated en métodos sincronos?


Bibliografía

Top comments (0)