View in English

  • Global Nav Open Menu Global Nav Close Menu
  • Apple Developer
Search
Cancel
  • Apple Developer
  • News
  • Discover
  • Design
  • Develop
  • Distribute
  • Support
  • Account
Only search within “”

Quick Links

5 Quick Links

Videos

Abrir menú Cerrar menú
  • Colecciones
  • Temas
  • Todos los videos
  • Información

Volver a WWDC25

  • Información
  • Resumen
  • Transcripción
  • Código
  • Explora la concurrencia en SwiftUI

    Descubre cómo SwiftUI aprovecha la concurrencia de Swift para crear apps seguras y receptivas. Explora cómo SwiftUI usa al actor principal de forma predeterminada y descarga el trabajo a otros actores. Aprende a interpretar anotaciones de concurrencia y a administrar tareas asincrónicas con el bucle de eventos de SwiftUI para lograr animaciones fluidas y actualizaciones de IU. Sabrás cómo evitar carreras de datos y escribir código sin miedo.

    Capítulos

    • 0:00 - Introducción
    • 2:13 - Actor principal: Meadows
    • 7:17 - Fallas de concurrencia
    • 16:53 - Campamento de código
    • 23:47 - Próximos pasos

    Recursos

    • Concurrency
    • Mutex
    • The Swift Programming Language: Concurrency
    • Updating an App to Use Swift Concurrency
      • Video HD
      • Video SD

    Videos relacionados

    WWDC25

    • Adopción de la concurrencia en Swift
    • Codificación conjunta: Mejora una app con la concurrencia en Swift

    WWDC23

    • Explore SwiftUI animation
  • Buscar este video…

    Hola, te doy la bienvenida a bordo. Soy tu guía turístico, Daniel, del equipo SwiftUI.

    Hoy veremos la concurrencia y el desarrollo de apps SwiftUI.

    Estás aquí porque escuchaste historias sobre los peligrosos errores de carrera de datos.

    Es posible que te hayas topado con algunos en el pasado.

    Como estados inesperados de apps, animaciones con fallos e incluso pérdidas de datos.

    Pero no te preocupes, este recorrido es 100% seguro. Porque con Swift y SwiftUI, dejamos las carreras de datos atrás. SwiftUI ejecuta tu código simultáneamente de varias maneras. Aprenderás a identificarlos con las anotaciones de concurrencia de SwiftUI. Espero que ganes confianza y valentía para tus propias aventuras con SwiftUI.

    Swift 6.2 agrega un modo de lenguaje, que marca tipos en un módulo con @MainActor.

    Todo lo que veremos se aplica con o sin este nuevo modo. Este recorrido tiene tres atracciones.

    Empezaremos desde Meadows en el actor principal y veremos cómo SwiftUI lo considera el valor predeterminado al compilar y ejecutar apps.

    Luego Concurrency Cliffs y cómo SwiftUI evita bloqueos en la IU moviendo trabajo fuera del hilo principal, mientras protege de carreras de datos.

    Finalmente, Camp, donde veremos la relación entre tu código concurrente y las API de SwiftUI.

    Vamos a nuestra primera parada: Meadows.

    Quiero recopilar combinaciones de colores naturales, así que creé una app para eso. Tomo una foto, elijo cuántos colores quiero y presiono Extract. La app seleccionará colores complementarios y los mostrará.

    Puedo desplazarme para ver los esquemas de color y elegir cuál exportar.

    Para la IU de extracción, creé una estructura ColorExtractorView.

    Se ajusta al protocolo de vista de SwiftUI, que declara el aislamiento del @MainActor.

    Swift usa el aislamiento de datos para comprender y verificar la seguridad de todos los estados mutables. En el recorrido, encontraremos muchos conceptos de concurrencia como ese. Si estás empezando o necesitas repasar, ve “Adoptar la concurrencia en Swift.” En SwiftUI, View está aislada en @MainActor y yo conformo mi estructura a View.

    Por lo que ColorExtractorView queda aislado en @MainActor. La línea punteada muestra un aislamiento inferido: se deduce al compilar, pero no está en el código que escribí. Que el tipo esté aislado con @MainActor implica que todos sus miembros también lo están.

    Esto incluye body, que cumple con el requisito de View, y otros elementos que declaro, como esta variable @State.

    Al cerrar el cuerpo de la vista, hago referencia a otras propiedades, como el esquema del modelo o su colorCount.

    El compilador permite esto porque el aislamiento @MainActor garantiza que sean seguros.

    Esto también se siente intuitivo.

    @MainActor es el valor predeterminado en tiempo de compilación de SwiftUI. Esto significa que puedo concentrarme en desarrollar las funcionalidades y no pensar en la concurrencia.

    No necesito anotar el código para fines de concurrencia. Es seguro automáticamente.

    Para hacer espacio para más código, voy a ocultar estos aislamientos inferidos.

    Este valor predeterminado en tiempo de compilación va más allá del código síncrono en mi vista.

    Los tipos de mi modelo de datos no necesitan ninguna anotación @MainActor.

    Como instancio el modelo en la declaración de la vista, Swift se asegura de que la instancia esté correctamente aislada.

    SchemeContentView tiene un gesto de toque que inicia la extracción de color.

    La función de extracción de color es asincrónica, por lo que uso una tarea para cambiar a un contexto asincrónico e invocarla.

    Como el cuerpo de la vista está aislado @MainActor, el cierre de esta tarea también se ejecuta en el hilo principal.

    El aislamiento de @MainActor es el valor predeterminado en tiempo de compilación de SwiftUI. Hace que escribir vistas sea cómodo y accesible. Pero hay otra razón muy práctica para eso. Las API de AppKit y UIKit están aisladas exclusivamente por @MainActor.

    SwiftUI interopera perfectamente con estas estructuras. Por ejemplo, el protocolo UIViewRepresentable refina View. Similar a una estructura, aísla UIViewRepresentable en @MainActor.

    Por lo tanto, un tipo que se ajusta a UIViewRepresentable también es View. Entonces @MainActor queda aislado.

    El inicializador de UILabel requiere aislamiento del @MainActor. Eso funciona en makeUIView porque es un miembro de mi tipo representable aislado con @MainActor.

    No es necesario anotarlo con @MainActor. SwiftUI anota sus API con @MainActor, porque refleja el comportamiento de ejecución que implementa.

    Estas anotaciones reflejan la semántica prevista de la estructura en tiempo de ejecución.

    Las anotaciones de concurrencia de SwiftUI expresan su semántica de tiempo de ejecución. Puede parecer una diferencia sutil con las que vimos antes, pero es fundamental. Veamos otro ejemplo que refuerza esta idea.

    Esta próxima parada va a ser emocionante. Ajusta tu cinturón de seguridad y asegura tus dispositivos electrónicos.

    Al sumar más funciones, si el hilo principal tiene mucha carga, la app puede tener saltos de marcos o interrupciones. Puedes usar tareas y concurrencia estructurada para descargar procesamiento del hilo principal. En “Eleva una app con concurrencia en Swift”, verás técnicas para mejorar el rendimiento de tu app. Asegúrate de entenderlo.

    El enfoque está en cómo SwiftUI aprovecha la concurrencia de Swift para mejorar el rendimiento de apps.

    El equipo de SwiftUI reveló que las animaciones integradas usan un hilo en segundo plano para calcular sus estados intermedios.

    Repasemos esto investigando este círculo en mi SchemeContentView.

    Cuando empieza y termina la extracción de color, el círculo se agranda y vuelve a su tamaño original con una animación.

    Para eso, uso un scaleEffect que reacciona a la propiedad isLoading.

    Cada marco de esta animación necesita un valor de escala diferente entre 1 y 1.5.

    Valores animados como esta escala implican matemáticas complejas. Calcular muchos de estos, fotograma por fotograma, puede ser costoso. SwiftUI realiza este cálculo en un hilo de segundo plano, para que el hilo principal tenga capacidad para otras cosas.

    Esta optimización también se aplica a las API que implementes.

    Así es. A veces, SwiftUI ejecuta tu código fuera del hilo principal.

    Pero no te preocupes, no es complicado.

    SwiftUI es declarativo. A diferencia de UIView, la estructura que se ajusta al protocolo View no es un objeto con ubicación fija en la memoria.

    En tiempo de ejecución, SwiftUI crea una representación separada para View.

    Esta ofrece oportunidades para muchos tipos de optimizaciones. Una es evaluar partes de la vista en un hilo en segundo plano.

    SwiftUI usa esta técnica cuando se realiza mucho procesamiento por parte del sistema. La mayoría de las veces implica cálculos geométricos de alta frecuencia.

    El protocolo Shape es un ejemplo de eso.

    El protocolo Shape requiere un método que devuelva una ruta.

    Hice una forma de cuña personalizada para representar un color extraído en mi rueda.

    Implementa ese método de ruta.

    Cada cuña tiene una orientación distinta. Mientras la forma se anima, el método de ruta recibe llamadas desde un hilo en segundo plano.

    Otro tipo de lógica personalizada es un argumento de cierre.

    En el centro del círculo están estos textos borrosos.

    Para implementar eso, uso un visualEffect en un texto SwiftUI.

    Modifica el radio de desenfoque entre dos valores según el cambio entre verdadero y falso. visualEffect incorpora un cierre para definir efectos en la vista del sujeto, es decir, el texto. Los efectos visuales pueden ser sofisticados y costosos.

    SwiftUI puede optar por llamar a este cierre desde un hilo en segundo plano.

    Entonces hay dos API que podrían invocar tu código desde un hilo en segundo plano.

    Visitemos rápidamente algunos más.

    El protocolo Layout puede invocar sus métodos requeridos fuera del hilo principal. Como visualEffect, el primer argumento de onGeometryChange es un cierre que también puede invocarse desde el hilo en segundo plano.

    Esta optimización en tiempo de ejecución con un hilo en segundo plano es parte de SwiftUI desde hace tiempo.

    SwiftUI expresa esto en tiempo de ejecución, o semántica, al compilador y a ti con la anotación Sendable.

    Otra vez, las anotaciones de concurrencia de SwiftUI expresan su semántica de tiempo de ejecución.

    Ejecutar tu código en un hilo separado libera el hilo principal y la app responde mejor.

    Sendable está para recordarte las posibles condiciones de carrera de datos cuando necesitas compartir datos de @MainActor.

    Piensa en Sendable como una señal de advertencia que dice “¡Peligro. No correr hacia aquí!”. Esa descripción quizás es muy dramática. Swift detectará cualquier condición de carrera en el código y te lo recordará con errores de compilación. La mejor estrategia para evitar condiciones de carrera es no compartir datos entre tareas concurrentes.

    Cuando una API de SwiftUI requiere una función sendable, la estructura te proporcionará variables como argumentos de la función. Aquí va un ejemplo:

    Hay un detalle en ColorExtactorView que no mostré. La rueda de color y el control deslizante tienen el mismo ancho, gracias a EqualWidthVStack.

    EqualWidthVStack es un diseño personalizado.

    Cómo realiza el diseño no es nuestro enfoque. El punto es que puedo hacer todos estos cálculos con el argumento de SwiftUI, sin usar variables externas.

    Pero ¿qué pasa si necesito acceder a variables externas a una función sendable?

    En SchemeContentView, necesito el pulso de estado en visualEffect.

    Pero Swift dice que existe una posible condición de carrera de datos.

    Saquemos los binoculares y veamos qué nos indica el error del compilador.

    La variable pulse es la abreviatura de self.pulse. Este es un caso común al compartir una variable aislada con @MainActor en cierres sendable.

    Self es una vista. Está aislada en el actor principal. Este es el punto de partida. Nuestro objetivo final es acceder a la variable pulse en un cierre sendable. Para lograr esto, deben ocurrir dos cosas.

    El valor self debe cruzar la frontera del actor principal hacia el código en hilos en segundo plano.

    En Swift, nos referimos a esto como enviar self al hilo en segundo plano.

    Esto requiere que self sea Sendable.

    Ahora que self aparece en el lugar correcto, queremos leer su propiedad pulse en esta región no aislada. El compilador no permitirá eso a menos que la propiedad pulse no esté aislada en ningún actor.

    Al ver el código nuevamente, como self es una View, está protegido por @MainActor.

    El compilador lo considera Sendable.

    Por eso, Swift permite que esta referencia a self cruce desde su aislamiento @MainActor hacia el cierre Sendable.

    En realidad, Swift nos advierte sobre el intento de acceder a pulse.

    Como miembro de View, pulse está aislada con @MainActor.

    El compilador dice que, aunque puedo enviar self, acceder a pulse aislada con @MainActor no es seguro.

    Para solucionar este error de compilación, puedo evitar leer la propiedad con una referencia a View.

    El efecto visual que estoy escribiendo no necesita todo el valor de esta vista. Solo quiere saber si pulse es verdadero o falso. Puedo hacer una copia de pulse en la lista de captura del cierre y hacer referencia a la copia. Así ya no envío self a este cierre.

    Envío una copia de pulse, que se puede enviar porque Bool es un tipo de valor simple.

    Esta copia solo existe dentro del alcance de esta función, por lo que acceder aquí no causa problemas de carrera de datos.

    En el ejemplo no pudimos acceder a pulse dentro de un cierre sendable porque está protegida por un actor global.

    Otra estrategia para que esto funcione es que todo lo que leemos no sea aislado.

    Muy bien, llegamos a Camp. Hablemos sobre cómo organizar tu código concurrente.

    Los desarrolladores con experiencia habrán notado que la mayoría de las API de SwiftUI son síncronas.

    Para invocar tu código simultáneo, debes cambiar a un contexto asíncrono con una tarea.

    Pero ¿por qué Button no acepta un cierre asíncrono?

    Las actualizaciones sincrónicas son importantes para la experiencia de usuario. Es importante si tu app tiene tareas de larga ejecución y hay que esperar resultados.

    Antes de iniciar una tarea larga con función asincrónica, debes actualizar la IU para indicar que la tarea está en progreso.

    Esta debe ser sincrónica, sobre todo si activa una animación sensible al tiempo.

    Imagina si le pido a un modelo de lenguaje que extraiga los colores. Ese proceso de extracción tardará. Entonces, en mi app, uso withAnimation para activar varios estados de carga.

    Cuando termina la tarea, invierto estos estados de carga con otro cambio de estado sincrónico.

    Las devoluciones de acción de SwiftUI aceptan cierres sincrónicos, necesarios para configurar actualizaciones de IU, como los estados de carga.

    Las funciones asincrónicas, por otro lado, requieren consideración adicional, en particular si hay animaciones. Vamos a explorar eso.

    Puedo desplazarme hacia arriba en mi app para ver un historial de los esquemas. A medida que cada esquema aparece, quiero que sus colores se revelen con una animación.

    El modificador de vista onScrollVisibilityChange me da el evento cuando el esquema de color aparece. Cuanto ocurre, activo una variable de estado en true para iniciar la animación, lo que actualiza el desplazamiento en Y de cada color con animación.

    SwiftUI debe lograr interacciones fluidas en cada fotograma, ya que los dispositivos exigen una cierta tasa de actualización.

    Ese es un contexto importante si quiero que mi código reaccione a un gesto continuo. Pongamos el código en la línea de tiempo.

    Voy a usar este triángulo para marcar cuando SwiftUI invoca onScrollVisibilityChange. El círculo marca cuando activo mi animación con una mutación de estado.

    Con esta configuración, que la mutación ocurra en el mismo marco que el gesto puede hacer una gran diferencia.

    Supongamos que quiero agregar algún trabajo asincrónico antes de la mutación animada. Marcaré el comienzo del trabajo asincrónico con una línea naranja y await.

    En Swift, esperar en una función asíncrona crea un punto de suspensión.

    Una Tarea acepta una función asíncrona como argumento.

    Cuando el compilador ve await, divide la función asíncrona en dos partes.

    Después de la primera parte, el tiempo de ejecución de Swift puede pausar esta función y realizar otras tareas en el CPU. Esto puede continuar por un periodo de tiempo arbitrario. Luego, el tiempo de ejecución se reanuda en la función asíncrona original y ejecuta su segunda parte.

    Este proceso puede repetirse para cada aparición de await en la función.

    En la línea de tiempo, esta suspensión hace que el cierre de la tarea se reanude después, superando el límite de actualización.

    Eso significa que mi animación se ve lenta y desfasada. Entonces una mutación en una función asincrónica puede no servir para lograr tu objetivo.

    SwiftUI brinda devoluciones de llamadas sincrónicas predeterminadamente. Esto evita la suspensión involuntaria del código asincrónico. Actualizar la IU en cierres de acciones sincrónicas es fácil de hacer correctamente. Siempre tienes la opción de usar una tarea para participar en un contexto asincrónico.

    La lógica sensible al tiempo, como la animación, requiere que la entrada y salida de SwiftUI sean síncronas. Las mutaciones sincrónicas de propiedades observables y las devoluciones de llamadas sincrónicas son las interacciones más naturales con la estructura. Una buena experiencia de usuario no tiene por qué implicar lógica concurrente personalizada. El código sincrónico es un buen punto de partida y llegada para muchas apps.

    Por otro lado, si tu app hace mucho trabajo simultáneo, intenta encontrar los límites entre el código IU y el código que no es IU.

    Es mejor separar la lógica del trabajo asincrónico de la lógica de visualización.

    Puedes usar una variable de estado como puente. El estado desacopla el código IU del código asincrónico.

    Puedes iniciar las tareas asincrónicas.

    Cuando termine una tarea asincrónica, realiza una mutación síncrona del estado para que la IU reaccione al cambio.

    Así, la lógica de la IU es mayoritariamente sincrónica.

    Como ventaja, será más fácil probar tu código asincrónico, ya que ahora está separado de la lógica de la IU.

    Tu vista aún puede usar una Tarea para cambiar a un contexto asincrónico.

    Pero intenta mantener simple el código en este contexto asincrónico. Está ahí para informar al modelo sobre un evento de IU.

    Detectar los límites entre la IU sensible al tiempo y la lógica asíncrona prolongada mejora la estructura de la app.

    Puede mantener las vistas sincronizadas y receptivas. También es importante organizar bien el código que no es de IU.

    Tendrás mayor libertad para hacerlo con los consejos que te mostré en este campamento.

    Swift 6.2 viene con una excelente configuración de aislamiento de actores. Si tienes una app, pruébala. Podrás eliminar la mayoría de tus anotaciones @MainActor.

    Mutex es una herramienta importante para hacer que una clase se pueda enviar. Consulta su documentación para saber cómo.

    Desafíate a escribir pruebas unitarias para el código asincrónico en tu app. Prueba a hacerlo sin importar desde SwiftUI.

    Muy bien. Así es como SwiftUI aprovecha la concurrencia de Swift para permitirte crear apps rápidas y sin carrera de datos.

    Espero que hayas adquirido un modelo mental sólido para la concurrencia en SwiftUI.

    • 2:45 - UI for extracting colors

      // UI for extracting colors
      
      struct ColorScheme: Identifiable, Hashable {
          var id = UUID()
          let imageName: String
          var colors: [Color]
      }
      
      @Observable
      final class ColorExtractor {
          var imageName: String
          var scheme: ColorScheme?
          var isExtracting: Bool = false
          var colorCount: Float = 5
      
          func extractColorScheme() async {}
      }
      
      struct ColorExtractorView: View {
          @State private var model = ColorExtractor()
      
          var body: some View {
                  ImageView(
                      imageName: model.imageName,
                      isLoading: model.isExtracting
                  )
                  EqualWidthVStack {
                      ColorSchemeView(
                          isLoading: model.isExtracting,
                          colorScheme: model.scheme,
                          extractCount: Int(model.colorCount)
                      )
                      .onTapGesture {
                          guard !model.isExtracting else { return }
                          withAnimation { model.isExtracting = true }
                          Task {
                              await model.extractColorScheme()
                              withAnimation { model.isExtracting = false }
                          }
                      }
                      Slider(value: $model.colorCount, in: 3...10, step: 1)
                          .disabled(model.isExtracting)
                  }
              }
          }
      }
    • 5:55 - AppKit and UIKit require @MainActor: an example

      // AppKit and UIKit require @MainActor
      // Example: UIViewRepresentable
      
      struct FancyUILabel: UIViewRepresentable {
          func makeUIView(context: Context) -> UILabel {
              let label = UILabel()
              // customize the label...
              return label
          }
      }
    • 6:42 - UI for extracting colors

      // UI for extracting colors
      
      struct ColorScheme: Identifiable, Hashable {
          var id = UUID()
          let imageName: String
          var colors: [Color]
      }
      
      @Observable
      final class ColorExtractor {
          var imageName: String
          var scheme: ColorScheme?
          var isExtracting: Bool = false
          var colorCount: Float = 5
      
          func extractColorScheme() async {}
      }
      
      struct ColorExtractorView: View {
          @State private var model = ColorExtractorModel()
      
          var body: some View {
                  ImageView(
                      imageName: model.imageName,
                      isLoading: model.isExtracting
                  )
                  EqualWidthVStack(spacing: 30) {
                      ColorSchemeView(
                          isLoading: model.isExtracting,
                          colorScheme: model.scheme,
                          extractCount: Int(model.colorCount)
                      )
                      .onTapGesture {
                          guard !model.isExtracting else { return }
                          withAnimation { model.isExtracting = true }
                          Task {
                              await model.extractColorScheme()
                              withAnimation { model.isExtracting = false }
                          }
                      }
                      Slider(value: $model.colorCount, in: 3...10, step: 1)
                          .disabled(model.isExtracting)
                  }
              }
          }
      }
    • 8:26 - Animated circle, part of color scheme view

      // Part of color scheme view
      
      struct SchemeContentView: View {
          let isLoading: Bool
          @State private var pulse: Bool = false
      
          var body: some View {
              ZStack {
                  // Color wheel …
      
                  Circle()
                      .scaleEffect(isLoading ? 1.5 : 1)
      
                  VStack {
                      Text(isLoading ? "Please wait" : "Extract")
      
                      if !isLoading {
                          Text("^[\(extractCount) color](inflect: true)")
                      }
                  }
                  .visualEffect { [pulse] content, _ in
                      content
                          .blur(radius: pulse ? 2 : 0)
                  }
                  .onChange(of: isLoading) { _, newValue in
                      withAnimation(newValue ? kPulseAnimation : nil) {
                          pulse = newValue
                      }
                  }
              }
          }
      }
    • 13:10 - UI for extracting colors

      // UI for extracting colors
      
      struct ColorExtractorView: View {
          @State private var model = ColorExtractor()
      
          var body: some View {
                  ImageView(
                      imageName: model.imageName,
                      isLoading: model.isExtracting
                  )
                  EqualWidthVStack {
                      ColorSchemeView(
                          isLoading: model.isExtracting,
                          colorScheme: model.scheme,
                          extractCount: Int(model.colorCount)
                      )
                      .onTapGesture {
                          guard !model.isExtracting else { return }
                          withAnimation { model.isExtracting = true }
                          Task {
                              await model.extractColorScheme()
                              withAnimation { model.isExtracting = false }
                          }
                      }
                      Slider(value: $model.colorCount, in: 3...10, step: 1)
                          .disabled(model.isExtracting)
                  }
              }
          }
      }
    • 13:47 - Part of color scheme view

      // Part of color scheme view
      
      struct SchemeContentView: View {
          let isLoading: Bool
          @State private var pulse: Bool = false
      
          var body: some View {
              ZStack {
                  // Color wheel …
      
                  Circle()
                      .scaleEffect(isLoading ? 1.5 : 1)
      
                  VStack {
                      Text(isLoading ? "Please wait" : "Extract")
      
                      if !isLoading {
                          Text("^[\(extractCount) color](inflect: true)")
                      }
                  }
                  .visualEffect { [pulse] content, _ in
                      content
                          .blur(radius: pulse ? 2 : 0)
                  }
                  .onChange(of: isLoading) { _, newValue in
                      withAnimation(newValue ? kPulseAnimation : nil) {
                          pulse = newValue
                      }
                  }
              }
          }
      }
    • 17:42 - UI for extracting colors

      // UI for extracting colors
      
      struct ColorExtractorView: View {
          @State private var model = ColorExtractor()
      
          var body: some View {
                  ImageView(
                      imageName: model.imageName,
                      isLoading: model.isExtracting
                  )
                  EqualWidthVStack {
                      ColorSchemeView(
                          isLoading: model.isExtracting,
                          colorScheme: model.scheme,
                          extractCount: Int(model.colorCount)
                      )
                      .onTapGesture {
                          guard !model.isExtracting else { return }
                          withAnimation { model.isExtracting = true }
                          Task {
                              await model.extractColorScheme()
                              withAnimation { model.isExtracting = false }
                          }
                      }
                      Slider(value: $model.colorCount, in: 3...10, step: 1)
                          .disabled(model.isExtracting)
                  }
              }
          }
      }
    • 18:55 - Animate colors as they appear by scrolling

      // Animate colors as they appear by scrolling
      
      struct SchemeHistoryItemView: View {
          let scheme: ColorScheme
          @State private var isShown: Bool = false
      
          var body: some View {
              HStack(spacing: 0) {
                  ForEach(scheme.colors) { color in
                      color
                          .offset(x: 0, y: isShown ? 0 : 60)
                  }
              }
              .onScrollVisibilityChange(threshold: 0.9) {
                  guard !isShown else { return }
                  withAnimation {
                      isShown = $0
                  }
              }
          }
      }
    • 0:00 - Introducción
    • SwiftUI aprovecha Swift Concurrency para ayudar a los desarrolladores a crear apps rápidas y sin carrera de datos. Swift 6.2 presenta un nuevo modo de lenguaje que marca todos los tipos en un módulo con @MainActor de manera implícita. SwiftUI ejecuta código simultáneamente de diversas maneras y proporciona anotaciones de concurrencia en sus API para ayudar a los desarrolladores a identificar y gestionar la concurrencia. La sesión se centra en comprender cómo SwiftUI gestiona la concurrencia para evitar las carreras de datos y mejorar el rendimiento de la app.

    • 2:13 - Actor principal: Meadows
    • El protocolo de vista de SwiftUI se encuentra aislado por @MainActor, lo que lo convierte en el valor predeterminado de tiempo de compilación para el código de IU. Esto significa que la mayor parte del código de IU se ejecuta implícitamente en el hilo principal, lo que ayuda a simplificar el desarrollo y garantizar la compatibilidad con UIKit y AppKit. Los modelos de datos instanciados dentro de una vista también se aíslan automáticamente. Las anotaciones de @MainActor de SwiftUI reflejan su comportamiento en el momento de ejecución y la semántica prevista, no solo las conveniencias del momento de compilación.

    • 7:17 - Fallas de concurrencia
    • SwiftUI usa subprocesos en segundo plano para tareas computacionalmente intensivas, como animaciones y cálculos de formas (por ejemplo, el método “path” del protocolo “Shape”, el cierre “visualEffect”, el protocolo “Layout", el cierre “onGeometryChange”) para evitar fallas en la IU. La anotación “Sendable” indica posibles condiciones de carrera de datos cuando se comparten datos entre el actor principal y los subprocesos en segundo plano. Para evitar las carreras de datos, minimiza el uso compartido de datos. Cuando sea necesario compartir datos, haz copias de estos.

    • 16:53 - Campamento de código
    • Las devoluciones de llamadas de acción de SwiftUI son síncronas por diseño para garantizar las actualizaciones inmediatas de la IU, especialmente para animaciones y estados de carga. Las tareas de larga ejecución deben iniciarse de forma asíncrona, pero las actualizaciones de la IU deben permanecer síncronas. Separa la lógica de la IU de la lógica no relacionada con la IU (asíncrona) usando el estado como un puente para activar las actualizaciones correspondientes una vez que se completen las tareas asíncronas. Mantén el código asíncrono en la vista simple y concéntrate en informar al modelo sobre los eventos de la IU. La lógica sensible al tiempo requiere que la entrada y la salida de SwiftUI sean síncronas.

    • 23:47 - Próximos pasos
    • Swift 6.2 viene con una excelente configuración de aislamiento de actores. Si tienes una app, pruébala. Podrás eliminar la mayoría de tus anotaciones @MainActor. Mutex es una herramienta importante para que una clase se pueda enviar. Consulta la documentación oficial para saber cómo hacerlo. Anímate a escribir pruebas unitarias para el código asíncrono en tu app. Intenta hacerlo sin importar SwiftUI.

Developer Footer

  • Videos
  • WWDC25
  • Explora la concurrencia en SwiftUI
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • App Store
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Fonts
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Open Source
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Sample Code
    • Tutorials
    • Downloads
    • Forums
    • Videos
    Open Menu Close Menu
    • Support Articles
    • Contact Us
    • Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Get the Apple Developer app.
    Copyright © 2025 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines