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
  • Adopción de la concurrencia en Swift

    Únete para aprender los conceptos básicos de la concurrencia en Swift. La concurrencia ayuda a mejorar la capacidad de respuesta y el rendimiento de la app, y Swift está diseñado para que sea más fácil escribir correctamente código asincrónico y concurrente. Mencionaremos los pasos que necesitas seguir para convertir una app de un solo subproceso a una concurrente. También te ayudaremos a determinar cómo y cuándo hacer el mejor uso de las características de concurrencia en Swift, ya sea haciendo que tu código sea más asincrónico, moviéndolo a un segundo plano o compartiendo datos entre tareas simultáneas.

    Capítulos

    • 0:00 - Introducción
    • 3:17 - Código de un solo subproceso
    • 6:00 - Tareas asincrónicas
    • 7:24 - Entrelazado
    • 10:22 - Introducción a la concurrencia
    • 11:07 - Funciones concurrentes
    • 13:10 - Código no aislado
    • 14:13 - Grupo de subprocesos concurrentes
    • 14:58 - Compartir datos
    • 15:49 - Tipos de valores
    • 17:16 - Tipos de actores aislados
    • 18:30 - Clases
    • 23:18 - Actores
    • 26:12 - Conclusión

    Recursos

    • Swift Migration Guide
    • The Swift Programming Language: Concurrency
      • Video HD
      • Video SD

    Videos relacionados

    WWDC25

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

    WWDC23

    • Beyond the basics of structured concurrency
  • Buscar este video…

    ¡Hola! Soy Doug del equipo Swift. Me emociona hablar contigo sobre cómo hacer el mejor uso de la concurrencia de Swift en tu app. La concurrencia permite que el código realice múltiples tareas al mismo tiempo. Puedes usar la simultaneidad en tu app para mejorar la capacidad de respuesta cuando esperas datos, como al leer archivos del disco o realizar una solicitud de red. Se puede utilizar para descargar cálculos costosos, como el procesamiento de imágenes grandes. El modelo de concurrencia Swift está diseñado para que sea más fácil escribir el código de forma correcta. Hace explícita la introducción de la concurrencia e identifica qué datos se comparten entre tareas concurrentes. Aprovecha esta información para identificar carreras de datos al momento de la compilación, para poder introducir concurrencia según lo necesites, sin temor a crear carreras difíciles de solucionar.

    Muchas apps solo necesitan usar la simultaneidad con moderación y algunas no la necesitan en absoluto. El código concurrente es más complejo que el código de un solo subproceso y solo debería introducirse cuando sea necesario.

    Las apps deberían comenzar ejecutando todo su código en el hilo principal y puedes llegar muy lejos con código de un solo hilo. El hilo principal es donde la app recibe eventos relacionados con la IU y puede actualizarla en respuesta. Si no realizas muchos cálculos en la app, está bien mantener todo en el hilo principal. Con el tiempo, es posible que necesites introducir código asincrónico, quizás para obtener contenido a través de la red. El código puede esperar a que el contenido llegue a través de la red sin provocar que la IU se bloquee. Si esas tareas tardan demasiado en ejecutarse, podemos trasladarlas a un hilo en segundo plano que se ejecute simultáneamente con el principal.

    A medida que desarrollamos más la app, descubrimos que mantener todos los datos dentro del hilo principal provoca que la app funcione mal. Podemos introducir tipos de datos para propósitos específicos que siempre se ejecutan en segundo plano.

    La concurrencia Swift proporciona herramientas como actores y tareas para expresar este tipo de operaciones concurrentes. Es probable que una app grande tenga una arquitectura parecida a ésta. Pero no se empieza ahí y no todas las apps tienen que acabar aquí. En esta sesión, analizaremos los pasos necesarios para llevar una app a través de este recorrido desde un solo subproceso a una concurrente. En cada paso, te ayudaremos a determinar cuándo dar ese paso, qué características del lenguaje Swift utilizarás, cómo usarlas efectivamente y por qué funcionan de esa manera. Primero, describiremos cómo funciona el código de un solo hilo con concurrencia Swift. Luego, presentaremos tareas asincrónicas para ayudar con operaciones de alta latencia, como el acceso a la red. Presentaremos la concurrencia para mover el trabajo a un hilo en segundo plano y aprenderemos cómo compartir datos entre hilos sin generar carreras de datos. Moveremos los datos fuera del hilo principal con actores. Comencemos con el código de un solo hilo. Cuando se ejecuta un programa, el código comienza a ejecutarse en el hilo principal. Cualquier código que agregues permanecerá en el hilo principal hasta que introduzcas simultaneidad para ejecutar el código en otro lugar. El código de un solo subproceso es más fácil de escribir y mantener, porque solo hace una cosa a la vez. Si luego comienzas a introducir concurrencia, Swift protegerá el código de su hilo principal.

    El hilo principal y todos sus datos están representados por el actor principal. No hay concurrencia en el actor principal, porque solo hay un hilo principal que puede ejecutarlo. Podemos especificar que los datos o el código están en el actor principal con la notación @MainActor. Swift garantizará que el código del actor principal solo se ejecute en el hilo principal y que a los datos del actor principal solo se acceda desde allí. Decimos que dicho código está aislado del actor principal. Swift protege el código del hilo principal con el actor principal de forma predeterminada. Esto es como si el compilador Swift escribiera “@MainActor” por ti en todo lo que hay en ese módulo. Nos permite acceder a estados compartidos y variables estáticas desde cualquier parte del código. En el modo de actor principal, no tenemos que preocuparnos por el acceso concurrente hasta introducimos la concurrencia. La protección del código con el actor principal depende de la configuración de compilación predeterminada. Utiliza esto principalmente para el módulo de la app principal y cualquiera que se centre en las interacciones de la IU. Este modo está habilitado de forma predeterminada para los nuevos proyectos de apps creados con Xcode 26. En esta charla, asumiremos que el modo de actor principal está habilitado en todos los ejemplos de código.

    Agreguemos un método a nuestro modelo de imagen para obtener y mostrar una imagen desde una URL. Queremos cargar una imagen desde un archivo local. Luego descodifícarlo y mostrarlo en la IU. La app no tiene concurrencia en absoluto. Sólo hay un único hilo principal que hace todo el trabajo. Toda esta función se ejecuta en el hilo principal en una sola pieza. Siempre que cada operación sea lo suficientemente rápida, estará bien.

    En este momento, solo podemos leer archivos localmente. Si queremos permitir que la app obtenga una imagen a través de la red, necesitamos usar una API diferente.

    Esta API URLSession nos permite obtener datos a través de la red dada una URL. Sin embargo, ejecutar este método en el hilo principal congelaría la IU hasta que se hayan descargado los datos de la red. Como desarrollador, es importante que tu app siga respondiendo. Esto significa tener cuidado de no bloquear el hilo principal durante tanto tiempo que la IU falle o se cuelgue. La concurrencia Swift proporciona herramientas para ayudar. Las tareas asincrónicas se pueden usar cuando se esperan datos, como una solicitud de red, sin saturar el hilo principal. Para evitar bloqueos como este, el acceso a la red es asincrónico. Podemos cambiar fetchAndDisplayImage para que maneje llamadas asincrónicas con la función 'async' y llamando a la API de sesión URL con 'await'. La espera indica dónde la función podría suspenderse, lo que significa que no se ejecuta en el hilo actual hasta que ocurra el evento que espera. Luego puede reanudar la ejecución.

    Podemos pensar en esto como dividir la función en dos partes: la pieza que se ejecuta hasta que buscamos la imagen y la pieza que se ejecuta después de que se buscó. Al dividir la función de esta manera, permitimos que otros trabajos se ejecuten entre las dos partes, manteniendo la IU responsiva.

    En la práctica, muchas API de biblioteca, como URLSession, descargarán trabajo en segundo plano. Aún no introdujimos la concurrencia en nuestro propio código, porque no la necesitábamos. Mejoramos la capacidad de respuesta haciendo que partes sean asincrónicas y llamando a API de biblioteca que descargan trabajo por nosotros. Todo lo que necesitábamos hacer en el código era adoptar async/await.

    Hasta ahora, el código solo ejecuta una función asíncrona. Una función asíncrona se ejecuta en una tarea. Una tarea se ejecuta independientemente de otro código y debe crearse para realizar una operación específica de principio a fin. Es común crear tareas en respuesta a un evento, como presionar un botón. La tarea realiza la operación completa de búsqueda y visualización de imágenes. Puede haber muchas tareas asincrónicas en una app determinada. Además de la tarea de búsqueda y visualización de imágenes de la que hablamos, agregué una segunda tarea que busca las noticias, las muestra y luego espera una actualización. Cada tarea completará las operaciones en orden de principio a fin. La obtención de datos se realiza en segundo plano, pero otras operaciones se ejecutarán en el hilo principal y solo se ejecutará una a la vez. Las tareas son independientes entre sí, por lo que cada tarea puede turnarse en el hilo principal. El hilo principal ejecutará las partes de cada tarea a medida que estén listas para ejecutarse. Un solo hilo que alterna entre múltiples tareas se llama 'entrelazado'. Esto mejora el rendimiento general al hacer el uso más eficiente de los recursos del sistema. Un hilo puede comenzar a progresar en cualquiera de las tareas lo antes posible, en lugar de dejar el hilo inactivo mientras esperas una sola operación. Si la obtención de la imagen se completa antes de obtener la noticia, el hilo principal comenzará a decodificar y mostrar la imagen antes de mostrar la noticia. Pero si la obtención de las noticias termina primero, el hilo principal puede mostrar las noticias antes de decodificar la imagen.

    Las tareas asincrónicas múltiples son ideales cuando la app necesita realizar muchas operaciones independientes al mismo tiempo. Cuando necesites realizar un trabajo en un orden específico, debes ejecutar ese trabajo en una sola tarea.

    Para que la app responda cuando hay operaciones de alta latencia, como una solicitud de red, usa una tarea asincrónica para ocultar esa latencia. Las bibliotecas pueden ayudarte brindándote API asincrónicas que pueden realizar concurrencia por ti, mientras el código permanece en el hilo principal. La API URLSession ya nos proporcionó cierta concurrencia, ya que gestiona el acceso a la red en un hilo en segundo plano. La operación de búsqueda y visualización de imágenes se ejecuta en el hilo principal. Podríamos descubrir que la operación de decodificación tarda demasiado tiempo. Podría aparecer cuando la IU se bloquea al decodificar una imagen grande.

    A menudo, para una app es suficiente que sea asincrónica y de un subproceso. Pero si notas que la app no responde, es una indicación de que están sucediendo demasiadas cosas en el hilo principal. Una herramienta de creación de perfiles como Instruments te ayudará a determinar dónde pasas demasiado tiempo. Si es un trabajo que se puede hacer más rápido sin concurrencia, hazlo antes. Si no se puede hacer más rápido, es posible que sea necesario introducir simultaneidad. La concurrencia permite que las partes del código se ejecuten en un hilo en segundo plano, en paralelo con el hilo principal, para que no bloquee la IU. También, se puede utilizar para realizar el trabajo más rápido utilizando más núcleos de CPU en el sistema. Nuestro objetivo es sacar la decodificación del hilo principal, para que el trabajo pueda realizarse en el hilo de fondo. Debido a que estamos en el actor principal por defecto, fetchAndDisplaylmage y decodelmage están aislados del actor principal. El código del actor principal puede acceder libremente a todos los datos y códigos a los que puede acceder el hilo principal, que es seguro porque no hay concurrencia.

    Queremos descargar la llamada a decodeImage, lo que podemos hacer aplicando el atributo @concurrent a la función decodeImage. @concurrent le dice a Swift que ejecute la función en segundo plano. Cambiar el lugar donde se ejecuta decodeImage también cambia nuestras suposiciones sobre el estado al que puede acceder decodeImage. Veamos cómo se implementa. La implementación verifica un diccionario de datos de imágenes en caché almacenado en el actor principal, lo cual solo es seguro hacer en el hilo principal. El compilador Swift nos muestra dónde la función intenta acceder a los datos del actor principal. Esto es lo que necesitamos saber para asegurarnos de no introducir errores cuando agregamos concurrencia. Hay algunas estrategias que puedes utilizar al romper vínculos con el actor principal para poder introducir la concurrencia de forma segura. En algunos casos, puedes mover el código del actor principal a un llamador que siempre se ejecuta en el actor principal. Esta es una buena estrategia si deseas asegurarte de que el trabajo se realice de manera sincrónica. O bien, puedes utilizar await para acceder al actor principal desde el código concurrente sincrónicamente.

    Si no es necesario que el código esté en el actor principal, puedes agregar la palabra clave nonaislada para separarlo de cualquier actor. Exploraremos la primera estrategia y hablaremos de las demás más adelante. Voy a mover el almacenamiento en caché de imágenes a fetchAndDisplayImage, que se ejecuta en el actor principal. Comprobar el caché antes de realizar una llamada asincrónica es bueno para eliminar la latencia. Si la imagen está en la memoria caché, fetchAndDisplayImage se completará sincrónicamente sin suspenderse. Es decir que los resultados se enviarán a la IU de inmediato y solo se suspenderá si la imagen aún no está disponible.

    Y podemos eliminar el parámetro URL de decodeImage ya que no lo necesitamos. Todo lo que tenemos que hacer es esperar el resultado de decodeImage.

    La función @concurrent siempre desactivará un actor para ejecutarlo. Si deseas que la función permanezca en el actor en el que fue llamada, puedes utilizar la palabra clave no aislada. Swift tiene formas adicionales de introducir más concurrencia. Para obtener más información, mira "Más allá de los conceptos básicos de la concurrencia estructurada".

    Si proporcionáramos API de decodificación como parte de una biblioteca para que las utilicen muchos clientes, @concurrent no siempre es la mejor opción. El tiempo que lleva decodificar los datos depende de su tamaño y se pueden decodificar pequeñas cantidades de datos en el hilo principal. Para las bibliotecas, es mejor ofrecer una API no aislada y dejar que los clientes decidan si desean delegar trabajo.

    El código no aislado es muy flexible, porque puedes llamarlo desde cualquier lugar: Si lo llamas desde el actor principal, permanecerá allí. Si lo llama desde un hilo en segundo plano, permanecerá allí. Esto lo hace una excelente opción para bibliotecas de propósito general. Cuando se transfiere trabajo a un segundo plano, el sistema se encarga de programar el trabajo para que se ejecute en un hilo en segundo plano. El grupo de subprocesos simultáneos contiene todos los subprocesos en segundo plano del sistema, lo que puede implicar cualquier número de subprocesos. En el caso de dispositivos más pequeños, como un reloj, es posible que solo haya uno o dos subprocesos en el grupo. Los sistemas grandes con más núcleos tendrán más subprocesos en segundo plano en el grupo. No importa en qué hilo de fondo se ejecute una tarea, y puedes confiar en que el sistema hará el mejor uso de los recursos. Por ejemplo, cuando una tarea se suspende, el hilo original comenzará a ejecutar otras tareas que estén listas. Al reanudarse, la tarea puede ejecutarse en cualquier hilo disponible del grupo, que podría no ser el mismo hilo en segundo plano donde comenzó.

    Ahora que tenemos concurrencia, compartiremos datos entre diferentes subprocesos. Compartir un estado mutable en el código es propenso a errores que conducen a bugs de ejecución difíciles de solucionar. Swift te ayuda a detectar estos errores en tiempo de compilación para que puedas escribir código simultáneo con confianza. Cada vez que pasamos del actor principal al grupo concurrente, compartimos datos entre diferentes subprocesos. Cuando obtenemos la URL de la interfaz de usuario, se pasa del actor principal al hilo de fondo para buscar la imagen. Al obtener la imagen se devuelven datos, que se pasan al decodificador de imágenes. Después de haber decodificado la imagen, ésta se devuelve al actor principal, junto con ella misma. Swift garantiza que se pueda acceder a todos estos valores en el código concurrente. Veamos qué sucede si la actualización de la IU termina creando tareas extra que involucran la URL. Afortunadamente, URL es un tipo de valor. Esto significa que cuando copiamos la URL en el hilo de fondo, el hilo tiene una copia separada de la que está en el hilo principal. Si el usuario ingresa una nueva URL a través de la IU, el código del hilo principal es libre de usar o modificar su copia y los cambios no tienen efecto en el valor usado en el hilo de fondo. Esto significa que es seguro compartir valores como URL porque, después de todo, en realidad no se trata de compartir: cada copia es independiente de las demás.

    Los tipos de valores fueron una gran parte de Swift desde el principio. Todos los tipos básicos, como cadenas, números enteros y fechas, son tipos de valor.

    Las colecciones de tipos de valores, como diccionarios y matrices, también son tipos de valores. Lo mismo ocurre con las estructuras y enumeraciones que almacenan tipos de valores en ellas, como esta estructura Post. Nos referimos a los tipos que siempre es seguro compartir de manera simultánea como tipos enviables. Sendable es un protocolo y cualquier tipo que se ajuste a Sendable puede compartirse de forma segura. Colecciones como Array definen conformidades condicionales con Sendable, por lo que son Sendable cuando sus elementos lo son. Se permite marcar las estructuras y enumeraciones como Enviables cuando todos sus datos de instancia son Enviables. Los tipos de actores principales son implícitamente Enviables, por lo que no es necesario decirlo explícitamente. Los actores como el actor principal protegen el estado no Enviable, asegurándose de que solo una tarea a la vez pueda acceder a él. Los actores pueden almacenar valores pasados a sus métodos y el actor puede devolver una referencia a su estado protegido desde sus métodos. Siempre que se envía un valor hacia o desde un actor, el compilador Swift verifica que sea seguro enviar el valor al código concurrente. Centrémonos en la llamada asíncrona a decodeImage.

    Decodificar imagen es un método de instancia, por lo que pasamos un argumento propio implícito.

    Vemos dos valores que se envían fuera del actor principal y un valor de resultado que se envía de regreso al actor principal. 'self' es la clase de modelo de imagen, que es el actor principal aislado. El actor principal protege el estado mutable, por lo que es seguro pasar una referencia a la clase al hilo de fondo. Los datos son un tipo de valor, por lo que se pueden enviar.

    Eso deja el tipo de imagen. Podría ser un tipo de valor, como Datos, en cuyo caso sería Enviable. En lugar de eso, hablemos de tipos que no se pueden enviar, como las clases. Las clases son tipos de referencia, lo que significa que cuando se asigna una variable a otra, apuntan al mismo objeto en la memoria. Si cambias algo sobre el objeto con una variable, como escalar la imagen, entonces esos cambios son inmediatamente visibles a través de las otras variables que apuntan al mismo objeto. fetchAndDisplayImage no utiliza el valor de la imagen simultáneamente. decodeImage se ejecuta en segundo plano, no puede acceder a ningún estado protegido por un actor. Crea una nueva instancia de una imagen a partir de los datos proporcionados. Ningún código concurrente puede hacer referencia a esta imagen, por lo que es seguro enviarla al actor principal y mostrarla en la IU. Veamos qué sucede cuando introducimos cierta concurrencia. Primero, este método scaleAndDisplay carga una nueva imagen en el hilo principal. La variable de imagen apunta a este objeto de imagen, que contiene la imagen del gato. La función crea una tarea que se ejecuta en el grupo simultáneo y que obtiene una copia de la imagen. Finalmente, el hilo principal pasa a mostrar la imagen. Ahora, tenemos un problema. El hilo de fondo cambia la imagen, hace que el ancho y la altura sean diferentes y reemplaza los píxeles con los de una versión escalada. Al mismo tiempo, el hilo principal está iterando sobre los píxeles en función del ancho y la altura anteriores. Esta es una carrera de datos. Podrías tener una falla en la IU o, lo más probable, un bloqueo, cuando tu programa intente acceder fuera del límite de la matriz de píxeles. La concurrencia Swift evita carreras de datos con errores del compilador si tu código intenta compartir un tipo que no se puede enviar. El compilador indica que la tarea concurrente está capturando la imagen, que también es utilizada por el actor principal para mostrar la imagen. Para corregirlo, debemos evitar compartir el mismo objeto de forma simultánea. Si queremos que el efecto de la imagen se muestre en la IU, debemos esperar a que se complete el escalado antes de mostrar la imagen. Podemos trasladar estas tres operaciones a la tarea para asegurarnos de que se realicen en orden. displayImage debe ejecutarse en el actor principal, por lo que usamos await para llamarlo desde una tarea concurrente. Si podemos hacer que scaleAndDisplay sea asincrónico directamente, podemos simplificar el código para no crear una nueva tarea y realizar tres operaciones en orden en la tarea que llama a scaleAndDisplay. Una vez enviada la imagen al actor principal para mostrarla en la IU, este puede conservar una referencia, por ejemplo, al almacenarla en caché. Si intentamos cambiar la imagen después de que se muestra en la IU, obtendremos error del compilador sobre acceso concurrente inseguro. Podemos solucionar el problema realizando cambios en la imagen antes de enviársela al actor principal.

    Si utilizas clases para su modelo de datos, es probable que comiencen en el actor principal, para que puedas presentar partes en la IU. Si finalmente decides que necesitas trabajar con ellos en un hilo en segundo plano, que no sean aislados. Pero, probablemente, no deberían ser Enviables. No deseas estar en una posición en la que parte del modelo se actualice en el hilo principal y otras se actualicen en el hilo de fondo. Mantener las clases del modelo como no enviables evita que se produzca este tipo de modificación simultánea. Es más fácil porque hacer a una clase Enviable generalmente requiere usar un mecanismo de sincronización de bajo nivel, como un bloqueo. Como las clases, los cierres pueden crear estados compartidos. Hay una función similar a una que teníamos anteriormente, que escala y muestra una imagen. Crea un objeto de imagen. Llama a perform(afterDelay:), proporcionándole un cierre que escala el objeto de imagen. Este cierre contiene otra referencia a la misma imagen. A esto lo llamamos captura de la variable imagen. Como las clases que no se pueden enviar, un cierre con estado compartido es seguro mientras no se lo llame simultáneamente. Solo haz que una función sea de tipo Enviable si necesitas compartirla simultáneamente.

    La verificación de envío se produce siempre que algunos datos pasan entre actores y tareas. Está ahí para garantizar que no haya carreras de datos que puedan provocar errores en la app. Muchos tipos comunes pueden enviarse y compartirse libremente entre tareas simultáneas. Las clases y los cierres pueden implicar un estado mutable que no es seguro compartir simultáneamente, así que úsalos desde una tarea a la vez.

    Puedes enviar un objeto de una tarea a otra, pero asegúrate de realizar todas las modificaciones en el objeto antes de enviarlo. Mover tareas asincrónicas a subprocesos en segundo plano puede liberar el subproceso principal para mantener la app en funcionamiento. Si descubres que tienes muchos datos sobre el actor principal que provocan que las tareas asincrónicas se "registren" con el hilo principal con demasiada frecuencia, quizá quieras introducir actores.

    A medida que la app crece con el tiempo, es posible que descubras que la cantidad de estado del actor principal también crece. Introducirás nuevos subsistemas para gestionar cuestiones como la administración del acceso a la red. Esto puede generar que una gran cantidad de estado resida en el actor principal, por ejemplo, el conjunto de conexiones abiertas manejadas por el administrador de red, al que accederíamos siempre que necesitáramos obtener datos a través de la red. Cuando comenzamos a utilizar estos subsistemas adicionales, la tarea de búsqueda y visualización de imágenes se volvió más complicada: intenta ejecutarse en el hilo de fondo, pero tiene que saltar al hilo principal porque ahí es donde están los datos del administrador de red. Esto puede generar contención, donde muchas tareas intentan ejecutar código en el actor principal al mismo tiempo. Las operaciones individuales pueden ser rápidas, pero si tienes muchas tareas haciendo esto, pueden generar fallas en la IU. Anteriormente, sacamos el código del hilo principal colocándolo en una función @concurrent. Todo el trabajo consiste en acceder a los datos del administrador de red. Para lograr esto, podemos introducir nuestro propio actor administrador de red. Al igual que el actor principal, los actores aíslan sus datos, por lo que solo puedes acceder a ellos cuando se ejecuta en ese actor. Junto con el actor principal, puedes definir tus propios tipos de actor. Un tipo de actor es similar a una clase de actor principal. Como una clase de actor principal, aislará sus datos de modo que solo un hilo pueda tocarlos a la vez. Un tipo de actor también es Enviable, por lo que puedes compartir libremente objetos de actor. A diferencia del actor principal, puede haber muchos objetos actor en un programa, cada uno de los cuales es independiente. Los objetos de actor no están vinculados a un único hilo como lo está el actor principal. Mover algún estado del actor principal a un objeto actor permitirá que se ejecute más código en un hilo en segundo plano, dejando abierto el hilo principal para mantener la IU responsiva.

    Utiliza actores cuando descubras que almacenar datos en el actor principal provoca que se ejecute demasiado código en el hilo principal. En ese punto, separa los datos de una parte del código que no sea de la IU, como el código de administración de red, en un nuevo actor.

    Ten en cuenta que es probable que la mayoría de las clases de la app no estén destinadas a ser actores: las orientadas a la IU deben permanecer en el actor principal para que puedan interactuar directamente con su estado. Las clases de modelo generalmente deben estar en el actor principal con la IU, o mantenerse no enviables, de modo que no fomente muchos accesos simultáneos al modelo. En esta charla, comenzamos con código de un solo hilo. Luego, introdujimos tareas asincrónicas para ocultar la latencia, código simultáneo para ejecutar en un hilo en segundo plano y actores para mover el acceso a los datos fuera del hilo principal. Con el tiempo, muchas apps seguirán este mismo curso.

    Utiliza herramientas de creación de perfiles para identificar cuándo y qué código mover fuera del hilo principal. La concurrencia Swift te ayudará a separar ese código del hilo principal correctamente, mejorando el rendimiento y la capacidad de respuesta de la app.

    Tenemos algunas configuraciones de compilación recomendadas de la app para ayudar con la introducción de la simultaneidad. La configuración de Concurrencia accesible habilita un conjunto de características futuras que facilitan el trabajo con simultaneidad. Recomendamos que todos los proyectos adopten esta configuración. Para los módulos Swift que interactúan con la IU, como el de app principal, recomendamos configurar el aislamiento en “MainActor”. Coloca el código en el actor principal a menos que dijeras lo contrario. Estas configuraciones funcionan en conjunto para que sea más fácil escribir apps de un subproceso y brindan una ruta más accesible para introducir simultaneidad. La concurrencia en Swift es una herramienta diseñada para ayudarte a mejorar la app. Úsala para introducir código asincrónico o concurrente cuando encuentres problemas de rendimiento con la app. La guía de migración de Swift 6 responde más preguntas sobre la concurrencia y el camino hacia la seguridad en la carrera de datos. Para ver cómo se aplican los conceptos de esta charla en la app de ejemplo, ve la charla complementaria con código. Gracias.

    • 3:20 - Single-threaded program

      var greeting = "Hello, World!"
      
      func readArguments() { }
      
      func greet() {
        print(greeting)
      }
      
      readArguments()
      greet()
    • 4:13 - Data types in a the app

      struct Image {
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
      }
      
      final class Library {
        static let shared: Library = Library()
      }
    • 4:57 - Load and display a local image

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) throws {
          let data = try Data(contentsOf: url)
          let image = decodeImage(data)
          view.displayImage(image)
        }
      
        func decodeImage(_ data: Data) -> Image {
          Image()
        }
      }
      
      final class Library {
        static let shared: Library = Library()
      }
    • 5:36 - Fetch and display an image over the network

      import Foundation
      
      struct Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) throws {
          let (data, _) = try URLSession.shared.data(from: url)
          let image = decodeImage(data)
          view.displayImage(image)
        }
      
        func decodeImage(_ data: Data) -> Image {
          Image()
        }
      }
      
      final class Library {
        static let shared: Library = Library()
      }
    • 6:10 - Fetch and display image over the network asynchronously

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = decodeImage(data)
          view.displayImage(image)
        }
      
        func decodeImage(_ data: Data) -> Image {
          Image()
        }
      }
      
      final class Library {
        static let shared: Library = Library()
      }
    • 7:31 - Creating a task to perform asynchronous work

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
        var url: URL = URL("https://swift.org")!
      
        func onTapEvent() {
          Task {
            do {
      	try await fetchAndDisplayImage(url: url)
            } catch let error {
              displayError(error)
            }
          }
        }
      
        func displayError(_ error: any Error) {
        }
      
        func fetchAndDisplayImage(url: URL) async throws {
        }
      }
      
      final class Library {
        static let shared: Library = Library()
      }
    • 9:15 - Ordered operations in a task

      import Foundation
      
      class Image {
        func applyImageEffect() async { }
      }
      
      final class ImageModel {
        func displayImage(_ image: Image) {
        }
      
        func loadImage() async -> Image {
          Image()
        }
        
        func onButtonTap() {
          Task {
            let image = await loadImage()
            await image.applyImageEffect()
            displayImage(image)
          }
        }
      }
    • 9:38 - Fetch and display image over the network asynchronously

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = decodeImage(data)
          view.displayImage(image)
        }
      
        func decodeImage(_ data: Data) -> Image {
          Image()
        }
      }
    • 10:40 - Fetch and display image over the network asynchronously

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = decodeImage(data, at: url)
          view.displayImage(image)
        }
      
        func decodeImage(_ data: Data, at url: URL) -> Image {
          Image()
        }
      }
    • 11:11 - Fetch over network asynchronously and decode concurrently

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = await decodeImage(data, at: url)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data, at url: URL) async -> Image {
          Image()
        }
      }
    • 11:30 - Implementation of decodeImage

      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = await decodeImage(data, at: url)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data, at url: URL) async -> Image {
          if let image = cachedImage[url] {
            return image
          }
      
          // decode image
          let image = Image()
          cachedImage[url] = image
          return image
        }
      }
    • 12:37 - Correct implementation of fetchAndDisplayImage with caching and concurrency

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          if let image = cachedImage[url] {
            view.displayImage(image)
            return
          }
      
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = await decodeImage(data)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data) async -> Image {
          // decode image
          Image()
        }
      }
    • 13:30 - JSONDecoder API should be non isolated

      // Foundation
      import Foundation
      
      nonisolated
      public class JSONDecoder {
        public func decode<T: Decodable>(_ type: T.Type, from data: Data) -> T {
          fatalError("not implemented")
        }
      }
    • 15:18 - Fetch over network asynchronously and decode concurrently

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = await decodeImage(data, at: url)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data, at url: URL) async -> Image {
          Image()
        }
      }
    • 16:30 - Example of value types

      // Value types are common in Swift
      import Foundation
      
      struct Post {
        var author: String
        var title: String
        var date: Date
        var categories: [String]
      }
    • 16:56 - Sendable value types

      import Foundation
      
      // Value types are Sendable
      extension URL: Sendable {}
      
      // Collections of Sendable elements
      extension Array: Sendable where Element: Sendable {}
      
      // Structs and enums with Sendable storage
      struct ImageRequest: Sendable {
        var url: URL
      }
      
      // Main-actor types are implicitly Sendable
      @MainActor class ImageModel {}
    • 17:25 - Fetch over network asynchronously and decode concurrently

      import Foundation
      
      class Image {
      }
      
      final class View {
        func displayImage(_ image: Image) {
        }
      }
      
      final class ImageModel {
        var imageCache: [URL: Image] = [:]
        let view = View()
      
        func fetchAndDisplayImage(url: URL) async throws {
          let (data, _) = try await URLSession.shared.data(from: url)
          let image = await self.decodeImage(data, at: url)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data, at url: URL) async -> Image {
          Image()
        }
      }
    • 18:34 - MyImage class with reference semantics

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scale(by factor: Double) {
        }
      }
      
      let image = MyImage()
      let otherImage = image // refers to the same object as 'image'
      image.scale(by: 0.5)   // also changes otherImage!
    • 19:19 - Concurrently scaling while displaying an image is a data race

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scaleImage(by factor: Double) {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        // Slide content start
        func scaleAndDisplay(imageName: String) {
          let image = loadImage(imageName)
          Task { @concurrent in
            image.scaleImage(by: 0.5)
          }
      
          view.displayImage(image)
        }
        // Slide content end
      
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 20:38 - Scaling and then displaying an image eliminates the data race

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scaleImage(by factor: Double) {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        func scaleAndDisplay(imageName: String) {
          Task { @concurrent in
            let image = loadImage(imageName)
            image.scaleImage(by: 0.5)
            await view.displayImage(image)
          }
        }
      
        nonisolated
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 20:54 - Scaling and then displaying an image within a concurrent asynchronous function

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scaleImage(by factor: Double) {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        @concurrent
        func scaleAndDisplay(imageName: String) async {
          let image = loadImage(imageName)
          image.scaleImage(by: 0.5)
          await view.displayImage(image)
        }
      
        nonisolated
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 21:11 - Scaling, then displaying and concurrently modifying an image is a data race

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scaleImage(by factor: Double) {
        }
      
        func applyAnotherEffect() {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        // Slide content start
        @concurrent
        func scaleAndDisplay(imageName: String) async {
          let image = loadImage(imageName)
          image.scaleImage(by: 0.5)
          await view.displayImage(image)
          image.applyAnotherEffect()
        }
        // Slide content end
      
        nonisolated
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 21:20 - Applying image transforms before sending to the main actor

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scaleImage(by factor: Double) {
        }
      
        func applyAnotherEffect() {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        // Slide content start
        @concurrent
        func scaleAndDisplay(imageName: String) async {
          let image = loadImage(imageName)
          image.scaleImage(by: 0.5)
          image.applyAnotherEffect()
          await view.displayImage(image)
        }
        // Slide content end
      
        nonisolated
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 22:06 - Closures create shared state

      import Foundation
      
      struct Color { }
      
      nonisolated class MyImage {
        var width: Int
        var height: Int
        var pixels: [Color]
        var url: URL
      
        init() {
          width = 100
          height = 100
          pixels = []
          url = URL("https://swift.org")!
        }
      
        func scale(by factor: Double) {
        }
      
        func applyAnotherEffect() {
        }
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
      
        // Slide content start
        @concurrent
        func scaleAndDisplay(imageName: String) async throws {
          let image = loadImage(imageName)
          try await perform(afterDelay: 0.1) {
            image.scale(by: 0.5)
          }
          await view.displayImage(image)
        }
      
        nonisolated
        func perform(afterDelay delay: Double, body: () -> Void) async throws {
          try await Task.sleep(for: .seconds(delay))
          body()
        }
        // Slide content end
        
        nonisolated
        func loadImage(_ imageName: String) -> MyImage {
          // decode image
          return MyImage()
        }
      }pet.
    • 23:47 - Network manager class

      import Foundation
      
      nonisolated class MyImage { }
      
      struct Connection {
        func data(from url: URL) async throws -> Data { Data() }
      }
      
      final class NetworkManager {
        var openConnections: [URL: Connection] = [:]
      
        func openConnection(for url: URL) async -> Connection {
          if let connection = openConnections[url] {
            return connection
          }
      
          let connection = Connection()
          openConnections[url] = connection
          return connection
        }
      
        func closeConnection(_ connection: Connection, for url: URL) async {
          openConnections.removeValue(forKey: url)
        }
      
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
        let networkManager: NetworkManager = NetworkManager()
      
        func fetchAndDisplayImage(url: URL) async throws {
          if let image = cachedImage[url] {
            view.displayImage(image)
            return
          }
      
          let connection = await networkManager.openConnection(for: url)
          let data = try await connection.data(from: url)
          await networkManager.closeConnection(connection, for: url)
      
          let image = await decodeImage(data)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data) async -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 25:10 - Network manager as an actor

      import Foundation
      
      nonisolated class MyImage { }
      
      struct Connection {
        func data(from url: URL) async throws -> Data { Data() }
      }
      
      actor NetworkManager {
        var openConnections: [URL: Connection] = [:]
      
        func openConnection(for url: URL) async -> Connection {
          if let connection = openConnections[url] {
            return connection
          }
      
          let connection = Connection()
          openConnections[url] = connection
          return connection
        }
      
        func closeConnection(_ connection: Connection, for url: URL) async {
          openConnections.removeValue(forKey: url)
        }
      
      }
      
      final class View {
        func displayImage(_ image: MyImage) {
        }
      }
      
      final class ImageModel {
        var cachedImage: [URL: MyImage] = [:]
        let view = View()
        let networkManager: NetworkManager = NetworkManager()
      
        func fetchAndDisplayImage(url: URL) async throws {
          if let image = cachedImage[url] {
            view.displayImage(image)
            return
          }
      
          let connection = await networkManager.openConnection(for: url)
          let data = try await connection.data(from: url)
          await networkManager.closeConnection(connection, for: url)
      
          let image = await decodeImage(data)
          view.displayImage(image)
        }
      
        @concurrent
        func decodeImage(_ data: Data) async -> MyImage {
          // decode image
          return MyImage()
        }
      }
    • 0:00 - Introducción
    • La concurrencia de Swift permite que las apps realicen tareas simultáneamente, lo que mejora la capacidad de respuesta y descarga el procesamiento en segundo plano. El modelo de concurrencia de Swift facilita escribir código concurrente de forma correcta al hacer explícita la introducción de la concurrencia, identificar los datos compartidos entre tareas concurrentes y detectar posibles carreras de datos en tiempo de compilación. Las apps comienzan ejecutando todo el código en el hilo principal. Al aumentar la complejidad, se pueden presentar tareas asincrónicas para operaciones de alta latencia, como el acceso a la red. Los hilos en segundo plano se pueden usar para tareas que requieren un mayor esfuerzo computacional. Swift brinda herramientas como actores y tareas para expresar estas operaciones concurrentes.

    • 3:17 - Código de un solo subproceso
    • En Swift, el código de un solo hilo se ejecuta en el hilo principal, que está aislado del actor principal. No hay concurrencia en el actor principal porque solo hay un hilo principal que puede ejecutarlo. Se puede especificar que los datos o el código están en el actor principal con la notación @MainActor. Swift garantizará que el código del actor principal solo se ejecute en el hilo principal y que a los datos del actor principal solo se acceda desde allí. Decimos que dicho código está aislado del actor principal. Swift protege el código del hilo principal mediante el actor principal, lo que garantiza que se pueda acceder libremente al estado compartido. La protección del código con el actor principal depende de la configuración de compilación predeterminada. Usa esto principalmente para el módulo de la app principal y cualquiera que se centre en las interacciones de la IU.

    • 6:00 - Tareas asincrónicas
    • La concurrencia en Swift usa async y await para que las funcionalidades no bloqueen la ejecución, permitiendo que otras tareas se realicen mientras se espera por datos, como solicitudes de red. Esto evita bloqueos y mejora la capacidad de respuesta de la IU al dividir las funcionalidades en partes que se ejecutan antes y después del evento esperado.

    • 7:24 - Entrelazado
    • Las funcionalidades asincrónicas se ejecutan en una tarea independientemente de otras tareas. Un solo hilo puede alternar entre tareas independientes a medida que estén listas, mediante “interleaving.” Esto mejora el rendimiento al evitar tiempos de inactividad y hace un uso eficiente de los recursos del sistema. Las tareas asincrónicas múltiples son efectivas cuando se realizan muchas operaciones independientes al mismo tiempo. Al realizar un trabajo en un orden específico, usa una sola tarea. El uso de tareas asincrónicas en un solo hilo suele ser suficiente. Si el hilo principal se sobrecarga, herramientas de análisis como Instruments pueden identificar cuellos de botella para optimizar el rendimiento antes de introducir concurrencia.

    • 10:22 - Introducción a la concurrencia
    • La concurrencia permite que partes del código se ejecuten en un hilo en segundo plano en paralelo con el hilo principal, lo que acelera el trabajo al aprovechar mejor los núcleos del CPU del sistema. Para mejorar el rendimiento, el ejemplo presenta concurrencia para ejecutar código en hilos en segundo plano, liberando el hilo principal.

    • 11:07 - Funciones concurrentes
    • Al aplicar el atributo @concurrent, se le indica a Swift que ejecute una funcionalidad en segundo plano. El compilador Swift resalta el acceso a los datos en el actor principal para presentar concurrencia de forma segura. Una práctica recomendada para garantizar que el trabajo se realice de manera sincrónica es mover el código del actor principal a un llamador que siempre se ejecute en el hilo principal.

    • 13:10 - Código no aislado
    • Una funcionalidad con @concurrent siempre cambiará de actor para ejecutarse. La palabra clave “nonisolated” permite elegir dónde ejecutar el código: en el hilo principal o en segundo plano. Para bibliotecas de propósito general, se recomienda ofrecer una API nonisolated y permitir que los clientes decidan si desean delegar el trabajo. Para más opciones de concurrencia, consulta “Más allá de los conceptos básicos de la concurrencia estructurada” de la WWDC23.

    • 14:13 - Grupo de subprocesos concurrentes
    • Al descargar trabajo en segundo plano, el sistema administra la programación del trabajo en hilos en un grupo de hilos concurrentes. Los dispositivos más pequeños pueden tener menos subprocesos en el grupo, mientras que los sistemas grandes de más núcleos tendrán más. Las tareas se asignan a los hilos disponibles en el grupo, que pueden cambiar a medida que las tareas se suspenden y se reanudan, optimizando el uso de recursos.

    • 14:58 - Compartir datos
    • Al trabajar con concurrencia y compartir datos entre hilos, existe el riesgo de introducir errores de tiempo de ejecución debido al acceso a un estado mutable compartido. El diseño de Swift mitiga este problema mediante verificaciones en tiempo de compilación, lo que permite a los desarrolladores escribir código concurrente con confianza.

    • 15:49 - Tipos de valores
    • El uso de tipos de valores brinda una ventaja significativa al tratar con tareas simultáneas. Cuando se copia un tipo de valor en un hilo en segundo plano, se crea una copia independiente, lo que garantiza que cualquier cambio realizado en el hilo principal no afecte el valor del hilo en segundo plano. Esta independencia hace que los tipos de valores sean seguros para compartir entre hilos. Los tipos de valores que cumplen con el protocolo “Sendable” son siempre seguros para compartir en contextos concurrentes. Los tipos de actores principales son implícitamente Sendable.

    • 17:16 - Tipos de actores aislados
    • Los actores Swift protegen el estado non-Sendable garantizando el acceso a una sola tarea. Cuando se envían valores hacia y desde los actores, el compilador Swift verifica la seguridad.

    • 18:30 - Clases
    • En Swift, las clases son tipos por referencia, por lo que los cambios realizados en un objeto desde una variable son visibles desde todas las variables que apuntan a ese objeto. Cuando varios hilos acceden y modifican al mismo tiempo un objeto non-Sendable, pueden producirse carreras de datos, fallos o errores visuales. El sistema de concurrencia de Swift evita esto en tiempo de compilación al exigir que solo los tipos Sendable se compartan entre actores y tareas. Para evitar carreras de datos, es fundamental que los objetos mutables no se compartan concurrentemente. Completa las modificaciones a los objetos antes de enviarlos a otra tarea o actor para su visualización o procesamiento. Si un objeto necesita modificarse en un hilo en segundo plano, márcalo como “nonisolated”, pero no como Sendable. Los cierres con estado compartido también pueden ser seguros siempre que no se invoquen concurrentemente.

    • 23:18 - Actores
    • A medida que una app crece, el actor principal puede administrar mucho estado, generando cambios de contexto. La introducción de actores puede mitigar esto. Los actores aíslan sus datos y permiten que solo un hilo a la vez acceda a ellos, evitando la contención. Al mover el estado del actor principal a actores dedicados, se puede ejecutar más código concurrentemente en subprocesos en segundo plano. Esto libera el hilo principal para mantener la capacidad de respuesta. Las clases orientadas a la IU y las clases de modelo deben permanecer en el actor principal o mantenerse como non-Sendable.

    • 26:12 - Conclusión
    • Las apps suelen comenzar con un solo subproceso y evolucionan para usar tareas asincrónicas, código simultáneo y actores para un mejor rendimiento. La concurrencia en Swift facilita esta transición, simplificando el traslado del código fuera del hilo principal y mejorando la respuesta. Las herramientas de creación de perfiles como Instruments permiten identificar cuándo y qué código debe trasladarse fuera del hilo principal. Usa la configuración de compilación recomendada para simplificar la introducción de concurrencia, y la configuración de Concurrencia accesible para habilitar funcionalidades futuras que faciliten el trabajo.

Developer Footer

  • Videos
  • WWDC25
  • Adopción de la concurrencia en Swift
  • 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