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

Vidéos

Ouvrir le menu Fermer le menu
  • Collections
  • Sujets
  • Toutes les vidéos
  • À propos

Plus de vidéos

  • À propos
  • Résumé
  • Transcription
  • Code
  • Adoptez la concurrence avec Swift

    Rejoignez-nous pour découvrir les concepts fondamentaux de concurrence en Swift. La concurrence vous aide à améliorer la réactivité et les performances des apps, et Swift est conçu pour faciliter l'écriture sans erreur de code asynchrone et concurrent. Nous passerons en revue les étapes nécessaires pour faire évoluer une app d'un fonctionnement monothread à un fonctionnement concurrent. Nous vous aiderons également à déterminer comment et quand tirer le meilleur parti des fonctionnalités de concurrence en Swift, qu'il s'agisse de rendre votre code plus asynchrone, de le déplacer en arrière-plan ou de partager des données entre des tâches concurrentes.

    Chapitres

    • 0:00 - Introduction
    • 3:17 - Code monothread
    • 6:00 - Tâches asynchrones
    • 7:24 - Entrelacement
    • 10:22 - Présentation de la concurrence
    • 11:07 - Fonctions concurrentes
    • 13:10 - Code non isolé
    • 14:13 - Pool de threads concurrents
    • 14:58 - Partage des données
    • 15:49 - Types de valeur
    • 17:16 - Types isolés par un acteur
    • 18:30 - Classes
    • 23:18 - Acteurs
    • 26:12 - Conclusion

    Ressources

    • Swift Migration Guide
    • The Swift Programming Language: Concurrency
      • Vidéo HD
      • Vidéo SD

    Vidéos connexes

    WWDC25

    • Code-along : Améliorez une app avec la concurrence Swift

    WWDC23

    • Beyond the basics of structured concurrency
  • Rechercher dans cette vidéo…

    Bonjour ! Je suis Doug, de l’équipe Swift, et je suis ravi de vous parler de la meilleure façon d’utiliser la concurrence Swift dans votre app. La concurrence permet au code d’effectuer plusieurs tâches en même temps. Vous pouvez utiliser la concurrence dans votre app pour plus de réactivité lors de l’attente de données, notamment lors de la lecture de fichiers sur le disque ou d’une requête réseau. Elle peut aussi servir à décharger des calculs coûteux en arrière-plan, comme lors du traitement de grandes images. Le modèle de concurrence Swift est conçu pour faciliter l’écriture correcte du code concurrent. Il rend explicite l’introduction de la concurrence et identifie les données partagées entre les tâches concurrentes. Grâce à ces infos, il identifie les courses de données pendant la compilation, afin que vous puissiez utiliser la concurrence selon les besoins, sans crainte de problèmes difficiles à corriger.

    De nombreuses apps ne recourent que rarement à la concurrence, et certaines n’en ont pas besoin du tout. Le code concurrent est plus complexe que le code monothread, et vous ne devez introduire la concurrence qu’en cas de besoin.

    Vos apps doivent d’abord exécuter tout leur code sur le thread principal, et vous pouvez aller très loin avec du code monothread. Le thread principal est celui où votre app reçoit les événements liés à l’interface et peut donc la mettre à jour. Si vous ne faites pas beaucoup de calculs dans votre app, vous pouvez tout garder sur le thread principal ! Vous aurez probablement besoin d’introduire du code asynchrone, notamment pour récupérer du contenu sur le serveur. Votre code peut attendre que le contenu traverse le réseau sans que l’interface utilisateur ne se bloque. Si l’exécution de ces tâches dure trop, nous pouvons les déplacer vers un thread d’arrière-plan exécuté en parallèle.

    À mesure que nous développons notre app, le maintien de toutes les données dans le thread principal peut générer une baisse de performances. Ici, nous pouvons introduire des types de données à des fins précises, toujours exécutées en arrière-plan.

    La concurrence Swift fournit des outils comme des acteurs et des tâches pour exprimer ce type d’opérations simultanées. Une grande app risque d’avoir une architecture similaire à celle-ci. Mais ceci n’est pas le point de départ, ni même le point d’arrivée de toutes les apps. Au cours de cette séance, nous parlerons des étapes à suivre pour faire passer une app du monothread à la concurrence. Nous vous aiderons tout du long à déterminer quand passer à cette étape, quelles fonctionnalités du langage Swift utiliser, comment le faire efficacement et pourquoi elles fonctionnent ainsi. Tout d’abord, nous décrirons le fonctionnement du code monothread avec la concurrence Swift. Ensuite, nous présenterons des tâches asynchrones pour faciliter les opérations à forte latence, comme l’accès au réseau. Ensuite, nous introduirons la concurrence pour déplacer le travail vers un thread d’arrière-plan et apprendrons à partager les données entre threads, sans conflits d’accès. Enfin, nous déplacerons les données hors du thread principal avec les acteurs. Commençons par le code monothread. Lors de l’exécution d’un programme, le code s’exécute d’abord sur le thread principal. Tout code ajouté reste sur le thread principal, jusqu’à ce que vous utilisiez explicitement la concurrence pour l’exécuter ailleurs. Le code monothread est plus facile à écrire et à gérer, car il ne fait qu’une chose à la fois. Si vous introduisez la concurrence ultérieurement, Swift protégera votre code de thread principal.

    Le thread principal et ses données sont représentés par le main actor. Il n’y a pas de concurrence sur le main actor, car il n’y a qu’un seul thread principal pouvant l’exécuter. On peut indiquer que les données ou le code sont sur le main actor via la notation @MainActor. Swift veille à ce que le code du main actor ne s’exécute que sur le thread principal et que ses données ne soient accessibles qu’à partir de là. Nous disons qu’un tel code est isolé auprès du main actor. Par défaut, Swift protège le code de votre thread principal en utilisant le main actor. C’est comme si le compilateur Swift écrivait pour vous @MainActor sur tout le contenu de ce module. Cela nous permet d’accéder librement à un état partagé, comme des variables statiques, partout dans notre code. En mode main actor, nous n’avons pas à nous soucier de l’accès concurrent tant que la concurrence n’est pas introduite. La protection du code avec le main actor est pilotée par défaut par un paramètre de build. Utilisez cela surtout pour votre module d’app principal et tous les modules concernant les interactions avec l’interface. Ce mode est activé par défaut pour les projets d’app créés avec Xcode 26. Dans cette vidéo, nous supposerons que le mode main actor est activé dans tous les exemples de code.

    Ajoutons une méthode à notre modèle d’image pour récupérer et afficher une image à partir d’une URL. Nous voulons charger une image à partir d’un fichier local. Puis la décoder et l’afficher dans notre interface. Notre app n’a aucune concurrence. Il n’y a qu’un seul thread principal qui fait tout le travail. Toute cette fonction s’exécute d’un seul tenant sur le thread principal. Tant que chaque opération est suffisamment rapide, c’est parfait.

    Pour l’instant, nous ne pouvons lire les fichiers que localement. Si nous voulons autoriser notre app à récupérer une image sur le réseau, nous devons utiliser une API différente.

    Cette API URLSession nous permet de récupérer des données sur le réseau à partir d’une URL. Toutefois, l’exécution de cette méthode sur le thread principal fige l’interface utilisateur jusqu’au téléchargement des données depuis le réseau. En tant que développeur, la réactivité de votre app est importante. Il faut donc veiller à ne pas bloquer longuement le thread principal pour éviter un bogue ou un blocage de l’interface. La concurrence Swift vous fournit des outils : les tâches asynchrones sont utilisables lors de l’attente de données, comme une requête réseau, sans bloquer le thread principal. Pour éviter de tels blocages, l’accès au réseau est asynchrone. Nous pouvons modifier fetchAndDisplayImage afin qu’il puisse gérer les appels asynchrones en rendant la fonction « async », et en appelant l’API de la séance d’URL avec « await ». « await » indique où la fonction peut être suspendue. Ainsi, elle cesse de s’exécuter sur le thread actuel jusqu’à ce que l’événement attendu se produise. Ensuite, elle peut reprendre son exécution.

    Cela s’apparente à une division de la fonction en deux parties : la partie qui s’exécute jusqu’à ce que l’on commence à récupérer l’image, et celle qui s’exécute après la récupération. Grâce à une telle scission, d’autres travaux peuvent s’exécuter entre les parties, tout en préservant la réactivité de l’interface.

    En pratique, de nombreuses API de bibliothèque, comme URLSession, déchargeront le travail en arrière-plan pour vous. Nous n’avons toujours pas introduit la concurrence dans notre propre code, car nous n’en avions pas besoin ! Nous avons amélioré la réactivité de notre app en rendant ses parties asynchrones et en appelant les API de bibliothèque qui déchargent le travail pour nous. Tout ce que nous avions à faire dans notre code était d’adopter async/await.

    Jusqu’à présent, notre code n’exécute qu’une fonction asynchrone. Une fonction asynchrone s’exécute dans une tâche. Une tâche s’exécute indépendamment des autres codes et doit être créée pour effectuer une opération spécifique de bout en bout. Il est courant de créer une tâche en réponse à un événement, tel qu’une pression sur un bouton. Ici, la tâche effectue toute l’opération de récupération et affichage de l’image. Il peut y avoir beaucoup de tâches asynchrones dans une app donnée. En plus de la tâche de récupération et affichage de l’image dont nous avons parlé, j’ai ajouté ici une deuxième tâche qui récupère les actualités, les affiche, puis attend une actualisation. Chaque tâche effectue ses opérations dans l’ordre, du début à la fin. La récupération se fait en arrière-plan, mais les autres opérations de chaque tâche s’exécutent toutes sur le thread principal, au rythme d’une opération à la fois. Les tâches sont indépendantes les unes des autres, aussi chaque tâche peut se dérouler à tour de rôle sur le thread principal. Le thread principal exécutera les parties de chaque tâche à mesure qu’elles seront prêtes à l’être. Un seul thread alternant entre plusieurs tâches est appelé « entrelacement ». Cela améliore les performances globales en optimisant l’utilisation des ressources système. Un thread peut commencer à progresser dès que possible sur n’importe quelle tâche, au lieu d’attendre une opération et de laisser le thread inactif. Si la récupération de l’image se termine en premier, le thread principal commencera à décoder et à afficher l’image avant d’afficher les actualités. Mais si la récupération des actualités se termine en premier, le thread principal peut afficher les nouvelles avant de décoder l’image.

    De multiples tâches asynchrones sont idéales lorsque votre app doit effectuer de nombreuses opérations indépendantes en même temps. Lorsque vous devez effectuer un travail dans un ordre spécifique, vous devez l’exécuter en une seule tâche.

    Pour rendre votre app réactive en cas d’opérations à forte latence comme une requête réseau, utilisez une tâche asynchrone pour masquer cette latence. Les bibliothèques peuvent vous aider, en fournissant des API asynchrones qui apportent une certaine concurrence, tandis que votre code reste sur le thread principal. L’API URLSession a déjà introduit une certaine concurrence, car elle gère l’accès au réseau sur un thread d’arrière-plan. Notre propre opération de récupération et d’affichage d’images s’exécute sur le thread principal. Il se peut que l’opération de décodage prenne trop de temps. Cela peut produire des blocages de l’interface lors du décodage d’une grande image.

    Une approche asynchrone et monothread suffit souvent pour une app. Mais si vous remarquez que votre app n’est pas réactive, c’est qu’il se passe trop de choses sur le thread principal. Un outil de profilage tel qu’Instruments peut vous aider à déterminer où vous passez trop de temps. S’il s’agit d’un travail qui peut être accéléré sans concurrence, faites-le en premier. S’il ne peut pas être accéléré, de la concurrence sera peut-être nécessaire. La concurrence permet à des parties de votre code de s’exécuter en arrière-plan, en parallèle du thread principal, pour ne pas bloquer l’interface. Elle s’utilise aussi pour accélérer le travail en utilisant plus de cœurs de CPU dans votre système. Nous voulons retirer le décodage du thread principal, afin de faire le travail sur le thread d’arrière-plan. Comme nous sommes en mode main actor par défaut, fetchAndDisplaylmage et decodelmage sont isolés auprès du main actor. Le code main actor peut librement accéder aux données et au code dont l’accès est réservé au thread principal, une sécurité, car il n’y a pas de concurrence sur le thread principal.

    Nous voulons décharger l’appel vers decodeImage, ce qui est possible en appliquant l’attribut @concurrent à la fonction decodeImage. @concurrent indique à Swift d’exécuter la fonction en arrière-plan. Le fait de changer le lieu d’exécution de decodeImage modifie aussi nos hypothèses sur l’état auquel decodeImage peut accéder. Intéressons-nous à la mise en œuvre. La mise en œuvre vérifie un dictionnaire de données d’image en cache qui est stocké sur le main actor, ce qui n’est sûr que sur le thread principal. Le compilateur Swift nous montre où la fonction essaie d’accéder aux données du main actor. C’est cela que nous devons savoir pour être sûrs de ne pas introduire de bogues lorsque nous ajoutons de la concurrence. Il existe quelques stratégies permettant de rompre les liens avec le main actor afin d’introduire de la concurrence en toute sécurité. Parfois, vous pouvez déplacer le code du main actor dans un appelant qui s’exécute toujours sur le main actor. C’est une bonne stratégie si vous voulez vous assurer que le travail s’effectue de manière synchrone. Vous pouvez aussi utiliser await pour accéder de manière asynchrone au main actor à partir d’un code concurrence.

    Si le code n’a pas besoin d’être sur le main actor, vous pouvez ajouter le mot-clé non isolé pour le séparer de tout acteur. Nous allons explorer la première stratégie, et nous parlerons des autres plus tard. Je vais déplacer la mise en cache de l’image dans fetchAndDisplayImage, qui s’exécute sur le main actor. La vérification du cache avant d’effectuer des appels asynchrones permet d’éliminer la latence. Si l’image se trouve dans le cache, fetchAndDisplayImage se termine de manière synchrone sans interruption. Les résultats seront immédiatement envoyés à l’interface et il n’y aura interruption que si l’image n’est pas déjà disponible.

    Et nous pouvons supprimer le paramètre url de decodeImage, car nous n’en avons plus besoin. Il ne nous reste plus qu’à attendre le résultat de decodeImage.

    Une fonction @concurrent se déconnectera toujours d’un acteur pour s’exécuter. Si vous souhaitez que la fonction reste sur l’acteur sur lequel elle a été appelée, utilisez le mot-clé non isolé. Swift propose d’autres moyens d’introduire plus de concurrence. Pour plus d’infos, consultez « Beyond the basics of structured concurrency ».

    Si nous fournissions des API de décodage dans le cadre d’une bibliothèque destinée à de nombreux clients, @concurrent ne serait pas toujours le meilleur choix. Le temps nécessaire au décodage des données dépend de la taille des données, et le décodage de petites quantités de données peut s’effectuer sur le thread principal. Pour les bibliothèques, il est préférable de fournir une API non isolée et de laisser les clients décider de décharger ou non le travail.

    Le code non isolé est très flexible, car vous pouvez l’appeler de n’importe où : si vous l’appelez du main actor, il restera sur le main actor. Si vous l’appelez à partir d’un thread d’arrière-plan, il restera sur un thread d’arrière-plan. Cela en fait un excellent choix par défaut pour les bibliothèques à usage général. Lorsque vous déchargez le travail en arrière-plan, le système gère la planification de l’exécution du travail sur un thread d’arrière-plan. Le pool de threads concurrents contient tous les threads d’arrière-plan du système, soit n’importe quel nombre de threads. Pour les petits appareils comme une montre, il peut n’y avoir qu’un ou deux threads dans le pool. Les grands systèmes avec plus de cœurs auront plus de threads d’arrière-plan dans le pool. Peu importe le thread d’arrière-plan sur lequel s’exécute une tâche, vous pouvez compter sur le système pour utiliser au mieux les ressources. Par exemple, lorsqu’une tâche est suspendue, le thread d’origine commence à exécuter d’autres tâches prêtes. Lorsque la tâche reprend, l’exécution peut se faire sur n’importe quel thread disponible dans le pool concurrent, qui peut être différent du thread d’arrière-plan initial.

    Maintenant que nous disposons de la concurrence, nous allons partager des données entre différents threads. Le partage d’états mutables dans du code concurrent est sujet à des erreurs qui produisent des bogues d’exécution difficiles à corriger. Swift vous aide à détecter ces erreurs lors de la compilation afin que vous puissiez écrire du code concurrent en toute confiance. Chaque fois que nous passons du main actor au pool concurrent, nous partageons des données entre différents threads. Lorsque nous recevons l’URL de l’interface utilisateur, elle passe du main actor au thread d’arrière-plan pour récupérer l’image. La récupération de l’image renvoie des données, qui sont transmises au décodage de l’image. Ensuite, après que nous avons décodé l’image, celle-ci est retransmise au main actor. Swift garantit que toutes ces valeurs sont accessibles en toute sécurité dans le code simultané. Voyons ce qui arrive si la mise à jour de l’interface finit par créer des tâches supplémentaires impliquant l’URL. Heureusement, l’URL est un type de valeur. Cela signifie que lorsque nous copions l’URL dans le thread d’arrière-plan, ce thread a une copie séparée de celle qui se trouve sur le thread principal. Si l’utilisateur saisit une nouvelle URL via l’interface, le code du thread principal est libre d’utiliser ou de modifier sa copie, et les modifications n’ont aucun effet sur la valeur utilisée dans le thread d’arrière-plan. Il n’y a donc pas de danger à partager des types de valeur de type URL, car ce n’est pas vraiment du partage : chaque copie est indépendante des autres.

    Les types de valeur sont une partie importante de Swift depuis le début. Tous les types de base - chaînes, entiers et dates - sont des types de valeurs.

    Les collections de types de valeurs, comme les dictionnaires et les tableaux, le sont aussi. De même pour les structs et les enums qui stockent des types de valeurs, comme cette struct Post. Nous nous référons aux types pouvant être partagés simultanément en toute sécurité, comme Sendable. Sendable est un protocole, et tout type conforme à Sendable peut être partagé en toute sécurité. Les collections comme Array définissent des conformités propres à Sendable, de sorte qu’elles sont Sendable lorsque leurs éléments le sont. Les structs et les enums peuvent être marquées Sendable quand toutes leurs données d’instance le sont. Les types main actor étant implicitement Sendable, vous n’avez pas à l’indiquer explicitement. Des acteurs comme le main actor protègent l’état non Sendable en veillant à ce qu’il ne soit accessible qu’à une seule tâche à la fois. Les acteurs peuvent stocker les valeurs transmises dans ses méthodes, et l’acteur peut renvoyer une référence à son état protégé depuis ses méthodes. Chaque fois qu’une valeur est envoyée dans ou hors d’un acteur, le compilateur Swift vérifie qu’elle peut être envoyée en toute sécurité au code simultané. Concentrons-nous sur l’appel asynchrone à decodeImage.

    Decode image est une méthode d’instance, nous transmettons donc un argument « self » implicite.

    Ici, nous voyons deux valeurs envoyées en dehors du main actor et une valeur de résultat renvoyée dans le main actor. « self » est ma classe de modèle d’image, qui est isolée du main actor. Le main actor protège l’état mutable, il est donc sûr de transmettre une référence à la classe au thread d’arrière-plan. Et Data est un type de valeur, donc il est Sendable.

    Reste le type d’image. Il peut s’agir d’un type de valeur, comme Data, auquel cas il peut être Sendable. Parlons plutôt des types qui ne sont pas Sendable, comme les classes. Les classes sont des types de référence, aussi lorsque vous affectez une variable à une autre, elles pointent vers le même objet en mémoire. Si vous modifiez quelque chose sur l’objet via une variable, comme la mise à l’échelle de l’image, ces modifications sont immédiatement visibles via les autres variables pointant vers le même objet. fetchAndDisplayImage n’utilise pas la valeur de l’image simultanément. decodeImage s’exécute en arrière-plan, il ne peut donc accéder à aucun état protégé par un acteur. Il crée une nouvelle instance d’une image à partir des données fournies. Cette image ne peut être référencée par aucun code concurrent. Vous pouvez donc l’envoyer en toute sécurité au main actor et l’afficher dans l’interface. Voyons ce qui se passe lorsque nous introduisons une certaine concurrence. Tout d’abord, cette méthode scaleAndDisplay charge une nouvelle image sur le thread principal. La variable image pointe vers cet objet image, qui contient l’image du chat. Ensuite, la fonction crée une tâche s’exécutant sur le pool concurrent, et une copie de l’image est créée. Enfin, le thread principal passe à l’affichage de l’image. Maintenant, nous avons un problème. Le thread d’arrière-plan modifie l’image : en modifiant la largeur et la hauteur et en remplaçant les pixels par ceux d’une version mise à l’échelle. Dans le même temps, le thread principal effectue une itération sur les pixels en fonction des anciennes largeur et hauteur. C’est une course de données. Vous pouvez vous retrouver avec un bogue d’interface, et surtout un plantage lorsque votre programme tente d’accéder à l’extérieur des limites du tableau de pixels. La concurrence Swift prévient les conflits de données avec des erreurs du compilateur si votre code tente de partager un type non Sendable. Ici, le compilateur indique que la tâche concurrente capture l’image, qui est également utilisée par le main actor pour afficher l’image. Pour corriger cela, nous devons veiller à ne pas partager le même objet simultanément. Si nous voulons que l’effet d’image soit affiché dans l’interface, la solution est d’attendre la fin de la mise à l’échelle avant d’afficher l’image. Nous pouvons déplacer ces trois opérations dans la tâche pour veiller à ce qu’elles se déroulent dans l’ordre. displayImage doit s’exécuter sur le main actor, nous utilisons donc « await » pour l’appeler depuis une tâche concurrente. Si l’on peut directement rendre scaleAndDisplay asynchrone, on peut simplifier le code pour ne pas créer de nouvelle tâche et effectuer les trois opérations dans l’ordre dans la tâche nommée scaleAndDisplay. Une fois l’image envoyée au main actor pour l’afficher dans l’interface, ce dernier peut stocker une référence à l’image, par exemple en mettant en cache l’objet image. Si l’on essaye de modifier l’image après son affichage dans l’interface, on reçoit un message d’erreur du compilateur au sujet d’un accès concurrent non sécurisé. Nous pouvons résoudre le problème en apportant des modifications à l’image avant de l’envoyer au main actor. Si vous utilisez des classes pour votre modèle de données, elles commenceront sans doute sur le main actor, afin que vous puissiez en présenter des parties dans l’interface. Si vous décidez finalement de travailler avec elles sur un thread d’arrière-plan, rendez-les non isolées. Mais elles ne devraient probablement pas être Sendable. Vous ne voulez pas être dans une situation où une partie du modèle est mise à jour sur le thread principal tandis que d’autres parties sont mises à jour sur le thread d’arrière-plan. Le fait de garder les classes de modèle non Sendable empêche ce type de modification simultanée de se produire. C’est aussi plus facile. Rendre une classe Sendable nécessite généralement un mécanisme de synchronisation de bas niveau comme un verrou. Comme les classes, les fermetures peuvent créer un état partagé. Voici une fonction similaire à une précédente, qui met à l’échelle et affiche une image. Elle crée un objet image. Ensuite, elle appelle perform(afterDelay:), en lui fournissant une fermeture qui met à l’échelle l’objet image. Cette fermeture contient une autre référence à la même image. Nous appelons cela une capture de la variable image. Comme les classes non Sendable, une fermeture avec état partagé reste sûre tant qu’elle n’est pas appelée simultanément. Ne rendez un type de fonction Sendable que si vous devez le partager simultanément.

    La vérification du statut Sendable a lieu à chaque transfert de données entre des acteurs et des tâches. Elle permet de s’assurer qu’il n’y a pas de conflits d'accès concurrent pouvant causer des bogues dans votre app. De nombreux types sont Sendable et peuvent être partagés librement entre des tâches concurrentes. Les classes et fermetures peuvent impliquer un état mutable dont le partage concurrent n’est pas sûr. Il faut donc les utiliser à partir d’une tâche à la fois.

    Vous pouvez toujours envoyer un objet d’une tâche à une autre, mais veillez à effectuer toutes les modifications sur l’objet avant son envoi. Le déplacement de tâches asynchrones vers des threads d’arrière-plan peut libérer le thread principal pour que votre app reste réactive. Si vous constatez un excès de données sur le main actor, ce qui génère un « enregistrement » trop fréquent de ces tâches asynchrones auprès du thread principal, vous voudrez peut-être introduire des acteurs.

    À mesure que votre app se développe, il se peut que la quantité d’état de le main actor augmente aussi. Vous introduirez de nouveaux sous-systèmes pour gérer des aspects comme l’accès au réseau. Cela peut produire un grand nombre d’états sur le main actor, par exemple toutes les connexions ouvertes gérées par le gestionnaire de réseau, auxquelles on accède chaque fois que l’on veut récupérer des données sur le réseau. Quand on commence à utiliser ces sous-systèmes supplémentaires, la tâche de récupération et d’affichage d’images de tout à l’heure devient plus compliquée : elle tente de s’exécuter sur le thread d’arrière-plan, mais elle doit passer par le thread principal, car c’est là que sont les données du gestionnaire de réseau. Cela peut conduire à des conflits, où de nombreuses tâches tentent d’exécuter du code sur le main actor en même temps. Les opérations individuelles peuvent être rapides, mais si vous avez beaucoup de tâches, cela peut aboutir à des problèmes d’interface. Plus tôt, nous avons déplacé le code hors du thread principal en le plaçant dans une fonction @concurrent. Ici, tout le travail consiste à accéder aux données du gestionnaire de réseau. Pour ce faire, nous pouvons introduire notre propre acteur de gestionnaire de réseau. Comme le main actor, les acteurs isolent leurs données, de sorte que vous ne pouvez accéder à ces données qu’en effectuant une exécution sur cet acteur. En plus du main actor, vous pouvez définir vos propres types d’acteurs. Un type d’acteur est similaire à une classe de main actor. Comme une classe de main actor, il isolera ses données afin qu’un seul thread ne puisse y accéder à la fois. Il existe un type d’acteur Sendable, ce qui vous permet de partager librement des objets acteurs. Contrairement au main actor, il peut y avoir de nombreux objets acteurs dans un programme, chacun d’entre eux étant indépendant. De plus, les objets acteurs ne sont pas liés à un seul thread comme le main actor. Ainsi, déplacer un état du main actor vers un objet acteur permettra d’exécuter plus de code sur un thread d’arrière-plan, laissant le thread principal ouvert pour que l’interface reste réactive.

    Utilisez des acteurs lorsque vous constatez que le stockage de données sur le main actor entraîne l’exécution d’une trop grande quantité de code sur le thread principal. À ce stade, séparez les données d’une partie de votre code non liée à l’interface, comme le code de gestion du réseau, dans un nouvel acteur.

    Sachez que la plupart des classes de votre app ne sont probablement pas destinées à être des acteurs : les classes orientées interface doivent rester sur le main actor afin d’interagir directement avec l’état de l’interface. Les classes de modèle doivent généralement se trouver sur le main actor avec l’interface, ou rester non Sendable, pour ne pas favoriser un grand nombre d’accès concurrents à votre modèle. Dans cet vidéo, nous avons commencé avec un code monothread. À mesure que nos besoins augmentaient, nous avons introduit des tâches asynchrones pour masquer la latence, du code concurrent à exécuter sur un thread d’arrière-plan, et des acteurs pour déplacer l’accès aux données hors du thread principal. Au fil du temps, de nombreuses apps suivront la même voie.

    Utilisez des outils de profilage pour identifier quel code déplacer hors du thread principal et à quel moment. La concurrence Swift vous aidera à séparer correctement ce code du thread principal, améliorant ainsi les performances et la réactivité de votre app.

    Nous avons recommandé des réglages de build pour votre app afin de faciliter l’introduction de la concurrence. Le réglage concurrence accessible active une suite de fonctionnalités à venir qui facilitent le travail avec la concurrence. Nous recommandons à tous les projets d’adopter ce réglage. Pour les modules Swift principalement en interaction avec l’interface, comme le module principal de votre app, nous conseillons aussi de paramétrer l’isolement des acteurs par défaut sur « MainActor ». Cela place le code sur le main actor, sauf indication contraire. L’association de ces réglages simplifie l’écriture d’apps monothread et offre un moyen plus simple d’introduire la concurrence lorsque vous en avez besoin. La concurrence Swift est un outil conçu pour vous aider à améliorer votre app. Utilisez-la pour introduire du code asynchrone ou concurrent lorsque vous rencontrez des problèmes de performances avec votre app. Le guide de migration vers Swift 6 peut vous aider à répondre à davantage de questions sur la concurrence et la sécurité en cas de courses de données. Et pour voir comment les présents concepts s’appliquent à un exemple d’app, veuillez regarder notre séance code-along. Merci.

    • 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 - Introduction
    • La simultanéité Swift permet aux apps d’exécuter plusieurs tâches à la fois, améliorant la réactivité et déchargeant les calculs en arrière-plan. Le modèle de simultanéité Swift facilite l’écriture correcte du code concurrent en rendant la gestion de la concurrence claire, en identifiant les données partagées entre tâches, et en détectant les conflits de données lors de la compilation. Les apps commencent par exécuter tout le code sur le thread principal. À mesure que la complexité augmente, on peut introduire des tâches asynchrones pour les opérations longues comme l’accès réseau. Les threads en arrière-plan sont utilisés pour les tâches plus lourdes en calcul. Swift fournit des outils tels que des acteurs et des tâches pour gérer ces opérations simultanées.

    • 3:17 - Code monothread
    • En Swift, le code monothread s’exécute sur le thread principal, isolé de l’acteur principal. Il n’y a pas de simultanéité sur l’acteur principal, car un seul thread peut l’exécuter. Les données ou le code peuvent être placés sur l’acteur principal via la notation @MainActor. Swift veille à ce que le code de l’acteur principal ne s’exécute que sur le thread principal et que ses données ne soient accessibles qu’à partir de là. On dit alors que ce code est isolé de l’acteur principal. Par défaut, Swift protège le code du thread principal avec l’acteur principal, ce qui permet d’accéder librement à l’état partagé. La protection du code avec l’acteur principal est pilotée par défaut par un paramètre de build. Utilisez-le principalement pour le module principal de l’app et les modules liés à I’UI.

    • 6:00 - Tâches asynchrones
    • La simultanéité Swift utilise async et await pour rendre les fonctions non bloquantes, ce qui permet d’exécuter d’autres tâches en attendant des données. Cela évite les blocages et améliore la réactivité de l’UI en séparant les fonctions avant et après l’évènement attendu.

    • 7:24 - Entrelacement
    • Les fonctions asynchrones s’exécutent dans une tâche, indépendamment des autres. Un seul thread peut alterner entre des tâches prêtes à s’exécuter grâce à l’entrelacement. Cela améliore les performances en évitant les temps morts et en optimisant l’utilisation des ressources. Les tâches asynchrones sont efficaces pour exécuter de nombreuses opérations indépendantes en même temps. Pour exécuter des tâches dans un ordre précis, utilisez une seule tâche. Des tâches asynchrones sur un seul thread suffisent souvent. Si le thread principal est surchargé, des outils comme Instruments permettent d’identifier les goulots d’étranglement à optimiser avant de recourir à la simultanéité.

    • 10:22 - Présentation de la concurrence
    • La simultanéité permet d’exécuter du code en arrière-plan en parallèle du thread principal, en tirant parti des cœurs CPU pour accélérer le travail. Pour améliorer les performances, l’exemple utilise la simultanéité pour exécuter du code en arrière-plan et libérer le thread principal.

    • 11:07 - Fonctions concurrentes
    • En appliquant l’attribut @concurrent, Swift est invité à exécuter une fonction en arrière-plan. Le compilateur Swift signale les accès aux données via l’acteur principal pour introduire la simultanéité en toute sécurité. Une bonne pratique consiste à déplacer le code de l’acteur principal dans un appelant qui s’exécute toujours sur le thread principal.

    • 13:10 - Code non isolé
    • Une fonction @concurrent se déconnectera toujours d’un acteur pour s’exécuter. Le mot-clé nonisolated permet aux clients de choisir où exécuter le code : sur le thread principal ou en arrière-plan. Pour les bibliothèques à usage général, mieux vaut proposer une API nonisolated et laisser le client décider de décharger le travail ou non. Pour aller plus loin, regardez « Beyond the basics of structured concurrency » de la WWDC23.

    • 14:13 - Pool de threads concurrents
    • Lorsque le travail est délégué en arrière-plan, le système le planifie sur des threads d’un pool concurrent. Les petits appareils ont moins de threads dans le pool, tandis que les systèmes plus puissants en ont davantage. Les tâches sont affectées aux threads disponibles, qui peuvent varier en fonction des pauses et des reprises des tâches, pour optimiser les ressources.

    • 14:58 - Partage des données
    • Avec la simultanéité, partager des données entre threads peut provoquer des bugs à l’exécution si l’état mutable est mal géré. Swift limite ce risque grâce à des vérifications à la compilation, ce qui aide les développeurs à écrire du code concurrent en toute confiance.

    • 15:49 - Types de valeur
    • L’utilisation des types valeur offre un réel avantage avec les tâches simultanées. Quand un type valeur est copié en arrière-plan, il devient indépendant : les changements sur le thread principal n’ont aucun impact sur lui. Cette indépendance permet de partager les types valeur en toute sécurité entre les threads. Les types valeur conformes au protocole Sendable peuvent toujours être partagés en toute sécurité. Les types liés à l’acteur principal sont implicitement Sendable.

    • 17:16 - Types isolés par un acteur
    • Les acteurs Swift protègent l’état non Sendable en garantissant un accès à une seule tâche. Lorsque des valeurs circulent entre acteurs, le compilateur Swift en vérifie la sécurité.

    • 18:30 - Classes
    • En Swift, les classes sont des types référence : toute modification via une variable est visible par toutes les autres qui pointent vers le même objet. Quand plusieurs threads modifient un objet non-Sendable en même temps, cela peut provoquer des conflits, des bugs ou des pannes. Le système de simultanéité de Swift empêche cela à la compilation en imposant que seuls les types Sendable soient partagés entre acteurs et tâches. Pour éviter les conflits d’accès, il est crucial de ne pas partager d’objets mutables simultanément. Terminez les modifications sur un objet avant de l’envoyer à une autre tâche ou acteur pour affichage ou traitement. Si un objet doit être modifié sur un thread d’arrière-plan, rendez-le nonisolated, sans le rendre Sendable. Les fermetures avec état partagé peuvent être sûres, tant qu’elles ne sont pas appelées simultanément.

    • 23:18 - Acteurs
    • À mesure que l’app grandit, l’acteur principal gère beaucoup d’états, entrainant des changements fréquents de contexte. L’introduction d’acteurs peut atténuer cela. Les acteurs isolent leurs données, accessibles par un seul thread à la fois, évitant ainsi les conflits. En déplaçant l’état vers des acteurs dédiés, plus de code peut s’exécuter en parallèle sur des threads d’arrière-plan. Cela libère le thread principal pour maintenir la réactivité. Les classes liées à l’UI ou au modèle doivent rester sur l’acteur principal ou être non-Sendable.

    • 26:12 - Conclusion
    • Les apps commencent souvent en monothread, puis évoluent vers l’asynchrone, la concurrence et les acteurs pour gagner en performance. La simultanéité Swift facilite cette transition en facilitant le déplacement du code hors du thread principal et en améliorant la réactivité. Les outils de profilage comme Instruments aident à identifier quand et quel code doit être retiré du thread principal. Utilisez les réglages de build recommandés pour faciliter l’ajout de la simultanéité, et activez l’option Approachable Concurrency pour profiter de nouvelles fonctionnalités qui en simplifient l’usage.

Developer Footer

  • Vidéos
  • WWDC25
  • Adoptez la concurrence avec 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