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
  • Optimiser les performances de SwiftUI avec Instruments

    Découvrez le nouvel instrument SwiftUI. Nous verrons comment les vues des mises à jour de SwiftUI, comment les modifications apportées aux données de votre application affectent ces mises à jour et comment le nouvel instrument vous aide à visualiser ces causes et effets. Pour tirer le meilleur parti de cette séance, nous vous recommandons de vous familiariser avec les apps d'écriture dans SwiftUI.

    Chapitres

    • 0:00 - Introduction et ordre du jour
    • 2:19 - Découvrez l’instrument SwiftUI.
    • 4:20 - Diagnostiquer et corriger les mises à jour longues de la vue du corps
    • 19:54 - Comprendre les causes et les effets des mises à jour de SwiftUI
    • 35:01 - Étapes suivantes

    Ressources

    • Analyzing the performance of your visionOS app
    • Improving app responsiveness
    • Measuring your app’s power use with Power Profiler
    • Performance and metrics
    • Understanding and improving SwiftUI performance
      • Vidéo HD
      • Vidéo SD

    Vidéos connexes

    WWDC25

    • Optimiser les performances du processeur avec Instruments

    WWDC23

    • Analyze hangs with Instruments
    • Demystify SwiftUI performance
    • Explore SwiftUI animation

    WWDC22

    • Compose custom layouts with SwiftUI

    WWDC21

    • Demystify SwiftUI

    Tech Talks

    • Explore UI animation hitches and the render loop
  • Rechercher dans cette vidéo…

    Bonjour. Je m’appelle Jed, de l’équipe Instruments. Et moi c’est Steven, de l’équipe Apple Music. Une app géniale, c’est une app qui marche. Un seul fragment de code peut suffire à ralentir votre app. Il est essentiel d’analyser votre app afin d’identifier les bouts de code qui pourraient entraîner des goulots d’étranglement, et d’y remédier pour que tout fonctionne. Durant cette session, nous verrons comment identifier les goulots d’étranglement présents dans votre code SwiftUI et comment rendre SwiftUI plus efficace. Tout d’abord, comment détecter un problème de performance ? Vous pouvez constater que votre app est moins réactive, qu’elle fonctionne par saccade ou se fige. La lecture des animations est hachée, ou le défilement est lent. La meilleure façon d’identifier les problèmes de performances est de profiler votre app à l’aide d’Instruments. Aujourd’hui, nous allons nous concentrer sur le diagnostic des problèmes de performances de code utilisant SwiftUI. Nous commencerons par une présentation du nouvel instrument SwiftUI inclus dans Instruments 26. Nous examinerons ensuite une app dont les mises à jour du corps de vue sont trop longues, pourquoi c’est un problème courant, et comment le détecter et le corriger avec Instruments. Enfin, nous nous pencherons sur les causes et les effets des mises à jour SwiftUI. Avec Instruments, nous identifierons les mises à jour inutiles et nous verrons comment les supprimer. Les problèmes de performances peuvent avoir de nombreuses causes, mais nous allons nous concentrer sur ceux qui proviennent de l’utilisation de SwiftUI.

    Si le problème de votre app n’est pas lié à votre code SwiftUI, nous vous recommandons de regarder « Analyze hangs with Instruments » et « Optimize CPU performance with Instruments » pour détecter le problème.

    Steven et moi avons travaillé ensemble sur une app. Steven, tu peux montrer ce qu’on a fait jusqu’à présent ? Merci, Jed ! L’app s’appelle Landmarks et présente des paysages époustouflants du monde entier. Elle indique la distance entre chaque site et ma position actuelle, ce qui me permet de rêver à ma prochaine destination, qu’elle soit à l’autre bout du monde ou à quelques heures de route ! L’app a l’air plutôt sympa pour l’instant, mais en la testant, j’ai remarqué que le défilement manquait parfois de fluidité. J’aimerais bien comprendre ce qui se passe. Jed, tu as parlé du nouvel outil Instruments SwiftUI. Tu peux nous faire une petite démo ? Bien sûr ! Instruments 26 offre une nouvelle manière d’identifier les problèmes de performances dans vos apps SwiftUI : Instruments nouvelle génération pour SwiftUI.

    Le nouveau modèle SwiftUI comprend plusieurs instruments pour vous aider à évaluer les performances de votre app. Tout d’abord, nous avons le nouvel instrument SwiftUI, dont je vous parlerai plus en détail dans un instant.

    Il y a aussi Time Profiler, qui affiche des exemples de tâches effectuées par votre app sur le CPU au fil du temps. Enfin, nous avons les instruments Hangs et Hitches, qui surveillent la réactivité de votre app.

    La première étape quand on recherche d’éventuels problèmes de performances dans une app, c’est d’examiner les infos générales fournies par Instruments SwiftUI.

    La première voie de la piste d’Instrument SwiftUI s’intitule Update Groups et indique quand SwiftUI s’exécute.

    Si l’utilisation du CPU monte en flèche alors que cette voie est vide, le problème ne vient probablement pas de SwiftUI. Les autres voies de la piste d’Instruments SwiftUI permettent d’identifier les mises à jour SwiftUI qui prennent trop de temps, et quand elles se produisent.

    Long View Body Updates vous indique quand la propriété « body » de votre vue prend trop de temps à s’exécuter. Long Representable Updates identifie les mises à jour des représentables de la vue et du contrôleur de vue qui peuvent prendre trop de temps.

    Enfin, Other Long Updates affichent tous les autres types de tâches SwiftUI qui sont trop longues. Ces trois voies offrent une vue d’ensemble de toutes les mises à jour qui prennent trop de temps et peuvent nuire aux performances. Les mises à jour s’affichent en orange ou en rouge, selon leur risque de provoquer des accrocs ou de figer l’app. Les accrocs ou les blocages de votre app suite à ces mises à jour peut dépendre de l’état de l’appareil, mais nous vous conseillons d’examiner les mises à jour trop longues, en commençant par celles en rouge.

    Pour commencer à utiliser Instruments SwiftUI, installez Xcode 26. Ensuite, mettez à jour l’appareil où vous comptez exécuter et profiler votre app vers la version logicielle la plus récente pour enregistrer les traces SwiftUI. Nous sommes prêts à procéder au premier profilage de l’app Landmarks. - Steven, c’est à toi ! - Merci, Jed !

    Le projet est déjà ouvert dans Xcode. Pour démarrer le profilage, j’appuie sur Commande et I. Xcode compile l’app en mode Release, puis lance automatiquement Instruments.

    Dans le sélecteur de modèles, je choisis le modèle SwiftUI et je clique sur le bouton d’enregistrement.

    Je commence par faire défiler la liste des sites. Il y a une étagère horizontale pour chaque continent.

    Je défile horizontalement jusqu’à la fin de l’étagère Amérique du Nord pour charger plus de vues.

    Ensuite, je clique sur Arrêter l’enregistrement.

    Une fois l’enregistrement arrêté, Instruments copie les données de profilage depuis mon appareil et les analyse. Une fois le traitement terminé, je peux utiliser Instruments SwiftUI pour voir si j’ai des problèmes de performance à régler. Je vais agrandir la fenêtre pour mieux voir tout ça.

    J’inspecte d’abord les voies générales de la piste d’Instruments SwiftUI qui présentent des mises à jour longues.

    Les corps de vue qui prennent trop de temps à s’exécuter causent souvent des problèmes de performances dans SwiftUI. Je vais donc inspecter la voie Long View Body Updates.

    Cette voie comporte plusieurs mises à jour longues en orange et en rouge, que je vais examiner. Je clique pour développer la piste SwiftUI.

    3 sous-pistes s’affichent : View Body Updates, Representable Updates et Other Updates. Chaque sous-piste présente des mises à jour longues en orange et en rouge, comme dans les voies générales. Les autres mises à jour sont en gris. Je sélectionne la piste View Body Updates.

    Dans le volet Détails ci-dessous, vous voyez un résumé hiérarchique de tous les corps de vue exécutés pendant ma session de profilage. Je développe le processus de mon app dans la hiérarchie, et j’ai une liste des modules de toutes les mises à jour du corps de la vue exécutées.

    Je peux filtrer ces résultats pour n’afficher que les mises à jour longues, en cliquant sur le menu déroulant et en sélectionnant le résumé Long View Body Updates.

    Le nombre d’entrées m’indique que plusieurs mises à jour longues doivent être examinées.

    Je clique pour développer le module de mon app. LandmarkListItemView a plusieurs mises à jour longues, je vais donc commencer par cette vue.

    En survolant le nom de la vue, une flèche apparaît.

    Je clique sur la flèche, un menu contextuel apparaît. Je choisis d’afficher les mises à jour pour voir une liste séquentielle de toutes les mises à jour longues pour le corps de cette vue.

    Je clique avec le bouton droit sur l’une des mises à jour longues, puis sur « Set Inspection Range and Zoom ».

    Cela définit la sélection dans ma trace sur l’intervalle correspondant à la mise à jour du corps de cette vue. Je clique ensuite sur la piste de l’instrument Time Profiler.

    C’est ici que je vois ce qui se passe au niveau du CPU quand le corps de ma vue s’exécute.

    Time Profiler collecte des données en échantillonnant à intervalles réguliers ce qui s’exécute sur le CPU. Pour chaque échantillon, il vérifie quelle fonction s’exécute et enregistre cette info dans la session de profilage. Le volet Détails du profil ci-dessous affiche les piles d’appels des échantillons enregistrés pendant le traçage. Dans ce cas, il s’agit d’échantillons enregistrés pendant l’exécution du corps de ma vue.

    Je vais maintenir la touche Option enfoncée et cliquer pour développer la pile d’appels du thread principal.

    Le travail SwiftUI est représenté par une pile d’appels très profonde. Ce qui m’intéresse le plus ici, c’est LandmarkListItemView. J’appuie sur Commande-F pour rechercher dans la pile d’appels, puis je saisis le nom dans le champ de recherche.

    Voici le corps de ma vue. Dans la colonne la plus à gauche, Time Profiler affiche le temps passé dans chaque frame de la pile d’appels.

    Cette colonne indique que la majeure partie du temps passé dans le corps de la vue a été consacrée à une propriété calculée appelée distance. Dans distance, les deux frames les plus lourds sont des appels à deux formateurs.

    Ce formateur de mesure et ce formateur de nombre. Revenons à Xcode pour examiner ce qui se passe dans le code.

    Voici LandmarkListItemView, qui est la vue pour chaque site figurant dans la liste.

    Et voici la propriété distance que j’ai remarquée dans Time Profiler. Cette propriété convertit la distance qui me sépare du site en une chaîne formatée à afficher dans la vue.

    Voici le formateur de nombres, que Time Profiler a signalé comme étant coûteux à créer.

    Et voici où le formateur de mesures crée la chaîne, qui représente également beaucoup de temps passé dans le corps de la vue.

    Dans le corps de la vue, je lis la propriété distance afin de créer le texte de l’étiquette. Cela se produit à chaque exécution du corps de la vue, et comme cela se passe sur le thread principal, mon app doit attendre que le texte distance soit formaté pour poursuivre la mise à jour de son UI.

    Pourquoi est-ce important ? Une milliseconde pour exécuter un corps de vue peut sembler minime, mais le temps total passé peut vite s’accumuler, surtout si SwiftUI a beaucoup de vues à mettre à jour à l’écran. Dis Jed, comment déterminer la durée nécessaire à SwiftUI pour exécuter les corps de vue ? Excellente question. Je vais d’abord expliquer comment fonctionne la boucle de rendu sur les plates-formes Apple. À chaque frame, l’app se réveille pour traiter les évènements, comme les pressions sur les touches. Elle met ensuite à jour l’UI. Cela inclut l’exécution de la propriété body de toutes les vues SwiftUI modifiées. Tout ce travail doit être terminé avant le délai de chaque frame. L’app transmet ensuite le travail au système, qui rend vos vues avant le délai de frame suivant. Le résultat rendu devient enfin visible à l’écran juste après ce délai. Tout fonctionne comme prévu. Les mises à jour sont terminées avant les délais de frame correspondants, ce qui laisse au système du temps pour rendre chaque frame et l’afficher à l’écran. Comparons cela avec une app dont le problème provient d’un corps de vue qui a pris trop de temps.

    Comme avant, nous traitons d’abord les évènements. Ensuite, nous exécutons les mises à jour de l’UI. Sur le premier frame ici, l’une des mises à jour de l’UI a pris trop de temps. Une portion de la mise à jour de l’UI s’est donc exécutée après le délai du frame. La mise à jour suivante ne va donc commencer qu’au frame suivant. Et ce frame n’est pas prêt à transmettre les données au moteur de rendu avant le délai.

    Par conséquent, le frame précédent reste visible à l’écran jusqu’à ce que le système ait fini de rendre le frame suivant. Nous appelons « accroc » un frame qui reste visible à l’écran trop longtemps, et retarde les frames suivants. Les accrocs rendent les animations moins fluides. Pour en savoir plus sur les accrocs, consultez l’article « Understanding hitches in your app » et cette Tech Talk, qui abordent la boucle de rendu en détail et expliquent comment corriger différents types d’accroc. Steven, est-ce que tu comprends l’importance du temps d’exécution du body ? Oui, tout à fait ! Une exécution trop longue des mises à jour du corps de la vue risque d’entraîner le dépassement du délai de frame de mon app, ce qui provoque des accrocs. Je dois donc calculer la chaîne de distance pour chaque site et la mettre en cache avant d’afficher la vue, plutôt que de le faire pendant l’exécution du corps. Revenons au code.

    Voici la propriété distance qui s’exécute à chaque mise à jour du corps de la vue. Au lieu d’effectuer cette tâche pendant l’exécution du corps de la vue, je vais la déplacer vers un emplacement plus centralisé : la classe où je gère les mises à jour de localisation.

    La classe LocationFinder est chargée de recevoir les mises à jour chaque fois que ma position change. Au lieu de calculer la chaîne de distance formatée dans le corps de la vue, je peux créer ces chaînes à l’avance et les mettre en cache ici afin qu’elles soient déjà calculées chaque fois qu’une de mes vues doit en afficher une.

    Je vais d’abord mettre à jour l’initialiseur afin de créer les formateurs précédemment créés dans le corps de vue.

    J’ai ajouté cette propriété formatter pour stocker mon formateur de mesure.

    En haut de l’initialiseur, je crée le formateur de nombres que je créais précédemment dans ma vue.

    Et le formateur de mesures, que je stocke dans la nouvelle propriété que j’ai ajoutée. Comme le format ne changera jamais, je peux réutiliser les formateurs chaque fois que les chaînes de distance doivent être mises à jour, et éviter ainsi le coût de recréer un formateur à chaque exécution du corps de vue. Ensuite, j’ai besoin de conserver les chaînes en cache afin que mes vues puissent les utiliser si nécessaire. Je vais ajouter du code pour gérer ces mises à jour.

    Je dispose d’un tableau pour stocker les sites, que j’utiliserai pour calculer les distances.

    J’ai aussi un dictionnaire pour mettre en cache les chaînes des distances calculées.

    Cette fonction appelée updateDistances recalculera les chaînes chaque fois que ma position changera.

    J’utilise ici le formateur pour créer le texte de la distance.

    Je stocke le texte dans mon cache ici.

    Dans un instant, j’appellerai cette dernière fonction depuis ma vue pour récupérer le texte mis en cache.

    Il me reste une dernière chose à faire ici. Lorsque ma position est mise à jour, je dois mettre à jour le cache des chaînes.

    Je clique sur le menu déroulant de la Jump Bar et passe à la fonction didUpdateLocations, que CoreLocation appelle lorsque ma position change.

    C’est ici que j’appelle la fonction updateDistances que j’ai créée.

    Maintenant, je vais revenir à ma vue.

    Je vais mettre à jour la vue pour utiliser la valeur mise en cache.

    Ces modifications devraient corriger la lenteur des mises à jour du corps de vue.

    Examinons maintenant une trace Instruments enregistrée après avoir appliqué ces corrections, pour vérifier si c’est mieux.

    Quand la piste View Body Updates est sélectionnée, le résumé Long View Body Updates du volet Détails indique que les mises à jour LandmarkListItemView trop longues ont disparu.

    Deux longues mises à jour du corps de la vue figurent toujours dans le résumé, mais il est important de noter qu’elles se produisent au tout début de la trace, lorsque l’app se prépare à afficher le premier frame. Les mises à jour effectuées juste après le lancement de l’app prennent souvent plus de temps, pendant que le système construit la hiérarchie initiale de l’app. Mais cela n’entraînera pas d’accroc. Ce qui importe ici, c’est que les longues mises à jour de LandmarkListItemView qui auraient pu causer des accrocs lors du défilement ont été corrigées et ont disparu de la liste. Je peux donc être certain que je ne ralentis pas SwiftUI quand il s’exécute pour afficher toutes mes vues à l’écran.

    Corriger les longues mises à jour du corps de vue est un excellent moyen d’améliorer les performances d’une app. Cependant, il y a un autre élément à prendre en compte : trop de mises à jour inutiles du corps de vue peuvent aussi nuire aux performances. Voyons pourquoi.

    Voici le diagramme que Jed a présenté tout à l’heure. Cette fois, aucune mise à jour n’est plus longue que les autres. En revanche, il y a un grand nombre de mises à jour relativement rapides qui doivent toutes avoir lieu pendant ce frame.

    Tout ce travail supplémentaire fait que l’app ne respecte pas le délai de soumission du frame. Une fois de plus, la mise à jour suivante a un frame de retard. Comme il n’y a rien à transmettre au moteur de rendu, cela crée un accroc, car le frame précédent reste visible pendant deux frames entiers. Si je mentionne l’impact potentiel des mises à jour de vue inutiles, c’est parce que je travaille actuellement sur une nouvelle fonctionnalité de notre app où cela me semble très important. Je fais défiler tous ces sites, et cela me donne très envie d’explorer de nouveaux endroits, mais c’est dur de savoir par où commencer. J’ai donc eu une idée pour rendre cela plus simple. Je vais vous montrer. J’ai ajouté un cœur à chaque site, sur lequel j’appuie pour l’ajouter ou le supprimer des favoris.

    Je vais vous montrer le code.

    Dans LandmarkListItemView, j’ai ajouté cette superposition qui affiche mon nouveau bouton en cœur.

    L’action du bouton appelle la fonction toggleFavorite de ma classe de données de modèle pour ajouter ou supprimer le site de mes favoris.

    L’icône de l’étiquette affiche un cœur plein si le site est ajouté aux favoris, ou un cœur vide s’il ne l’est pas.

    Je vais cliquer sur toggleFavorite tout en maintenant la touche Commande enfoncée pour accéder à cette fonction.

    Et voici comment j’ajoute un favori.

    Le modèle stocke un tableau des sites favoris. J’ajoute le site au tableau lorsqu’il est ajouté aux favoris.

    Pour supprimer un favori, je procède de manière inverse.

    Et voilà où j’en suis pour l’instant. Je suis sûr que ma fonctionnalité peut encore être améliorée, mais c’est bien de la profiler dans Instruments dès le départ, puis régulièrement pendant le développement. Voyons comment fonctionne ma nouvelle fonctionnalité. Je vais appuyer sur Commande et I pour compiler l’app, revenir à Instruments et cliquer à nouveau sur Enregistrer.

    Je fais défiler la liste jusqu’à Amérique du Nord, puis vers la droite, et j’appuie sur le cœur pour ajouter Muir Woods à mes favoris. Ce n’est pas très loin de chez moi, mais je n’y suis encore jamais allé ! Bon, maintenant, je remonte. J’ajoute à mes favoris un lieu lointain. Pourquoi pas le mont Fuji ? Ce serait une aventure géniale. Je vais maintenant arrêter l’enregistrement.

    Je veux m’assurer que le fait d’appuyer sur mon nouveau bouton favori ne provoque pas de mises à jour supplémentaires inutiles. Analyser une trace en ayant une idée précise de vos attentes et rechercher tout ce qui semble anormal peut être un excellent moyen d’identifier les problèmes potentiels.

    Je vais cliquer pour développer la piste d’instrument SwiftUI et sélectionner la sous-piste View Body Updates.

    Comme j’ai ajouté deux favoris, Muir Woods et le mont Fuji, je m’attends à ce que ces deux vues aient été mises à jour. J’ai appuyé sur les boutons dans la seconde moitié de la trace, après avoir fait défiler vers le bas. Je vais donc mettre en surbrillance cette partie de la trace pour me concentrer sur ce qui m’intéresse.

    Je vais maintenant vérifier le volet Détails ci-dessous. Je développe la hiérarchie pour trouver la liste des mises à jour de mes vues.

    Je suis surpris de voir que LandmarkListItemView a en fait été mis à jour plusieurs fois. Pourquoi ? Lorsque je débogue une mise à jour de vue dans une app UIKit, je place un point d’arrêt dans mon code et j’inspecte la trace d’appel pour comprendre pourquoi la vue a été mise à jour. Cependant, dans mes apps SwiftUI comme Landmarks, cela ne fonctionne pas bien. Les piles d’appels SwiftUI semblent plus difficiles à comprendre. Jed, pourquoi cette approche ne fonctionne pas avec les apps SwiftUI ? Je vais vous expliquer. Xcode aide à comprendre les causes et effets du code impératif, comme dans les apps UIKit, en affichant des traces d’appel lorsque le point d’arrêt est atteint. UIKit est un framework impératif, les traces d’appel permettent souvent de déboguer les causes et effets. Ici, je vois que mon étiquette est mise à jour, car j’ai défini une propriété isOn dans mon viewDidLoad. En devinant les noms de certains frames système dans la trace d’appel, il semble que cela s’est produit pendant le lancement de la première scène de mon app. Lorsque je compare avec une app SwiftUI similaire qui fait la même chose, je constate plusieurs mises à jour récursives dans SwiftUI, séparées par des frames dans quelque chose appelé AttributeGraph. Rien de tout cela ne m’explique pourquoi ma vue doit être mise à jour.

    SwiftUI étant déclaratif, la trace d’appel ne permet pas de comprendre pourquoi votre vue est mise à jour. Comment comprendre ce qui provoque la mise à jour de mes vues SwiftUI ? Tout d’abord, vous devez comprendre le fonctionnement de SwiftUI.

    Je vais présenter un exemple de vue pour montrer comment le modèle de données de SwiftUI, AttributeGraph, définit les dépendances entre les vues et évite de réexécuter une vue sauf si cela est nécessaire. Je ne vais pas aborder tous les détails, mais cela devrait vous aider à comprendre comment les mises à jour circulent dans votre app.

    Les vues déclarent leur conformité au protocole View. Ensuite, elles implémentent une propriété body pour définir leur apparence et leur comportement en renvoyant une autre valeur View.

    Ici, OnOffView renvoie une vue Text à partir de son corps et transmet une étiquette qui change en fonction de la valeur de sa variable d’état isOn.

    Lorsque cette vue est ajoutée pour la première fois à la hiérarchie des vues, SwiftUI reçoit un objet appelé attribut de sa vue parent qui stocke la structure de la vue. Les structures de vue sont recréées fréquemment, mais les attributs conservent leur identité et maintiennent leur état pendant toute la durée de vie de la vue. Ainsi, lorsque la vue parent est mise à jour, la valeur de cet attribut change, mais son identité reste la même. La vue est invitée à créer ses propres attributs pour stocker son état et définir son comportement. Elle crée d’abord un espace de stockage pour la variable d’état isOn, puis un attribut qui suit les modifications de cette variable d’état. Ensuite, la vue crée un nouvel attribut pour exécuter son corps, qui dépend de ces deux éléments. Chaque fois que l’attribut du corps de vue est invité à produire une nouvelle valeur, il lit la valeur actuelle de votre vue transmise par la vue parent. Ensuite, l’attribut met à jour une copie de cette structure de vue avec la valeur actuelle de votre variable d’état. Il accède ensuite à la propriété calculée « body » sur cette copie temporaire de votre vue et enregistre la valeur qu’elle renvoie comme valeur mise à jour de l’attribut. Ensuite, comme le corps de votre vue a renvoyé une vue Text, SwiftUI configure les attributs dont il a besoin pour afficher le texte.

    La vue Text crée un attribut qui dépend de l’environnement pour accéder aux styles par défaut actuels, tels que la couleur de premier plan et la police, afin de déterminer l’apparence du texte rendu. Cet attribut ajoute une dépendance à votre vue body pour accéder à la chaîne qu’il rendra à partir de la structure Text que vous avez renvoyée. Enfin, Text crée un autre attribut qui construit une description de ce qui doit être rendu en fonction du texte stylisé.

    Maintenant, parlons de ce qui se passe lorsque vous modifiez une variable d’état. Lorsque vous faites cela, SwiftUI ne met pas immédiatement à jour vos vues. Au lieu de cela, il crée une transaction. Une transaction représente une modification de la hiérarchie de vues SwiftUI qui doit être effectuée avant le frame suivant.

    Cette transaction marquera l’attribut signal de votre variable d’état comme obsolète. Ensuite, lorsque SwiftUI est prêt à se mettre à jour pour le frame suivant, il exécute la transaction et applique la mise à jour prévue. Maintenant qu’un attribut a été marqué comme obsolète, SwiftUI parcourt la chaîne d’attributs qui dépendent de l’attribut désormais obsolète, en marquant chacun d’eux comme obsolète à l’aide d’un indicateur. L’indicateur est défini très rapidement et aucune autre tâche n’est effectuée pour le moment. Après avoir exécuté toutes les autres transactions, SwiftUI doit déterminer ce qu’il doit dessiner à l’écran pour ce frame. Cependant, il ne peut pas accéder à ces infos car elles sont marquées comme obsolètes.

    SwiftUI doit donc mettre à jour toutes les dépendances de ces infos pour déterminer ce qu’il doit dessiner.

    Il commence par celles qui n’ont pas de dépendances obsolètes, comme le signal State. Maintenant, l’attribut view body est prêt à être mis à jour. Il s’exécute à nouveau, produit une nouvelle valeur de structure Text avec une chaîne à jour. Cette valeur est transmise à l’attribut Apply styling existant et les mises à jour se poursuivent jusqu’à ce que tous les attributs nécessaires pour déterminer ce qui doit être dessiné soient à jour. SwiftUI peut désormais répondre à la question pour laquelle il a été conçu : que doit-il dessiner à l’écran ?

    Lorsque je demande « pourquoi le corps de ma vue s’est-il exécuté ? », la vraie question est « qu’est-ce qui a marqué le corps de ma vue comme obsolète ? ». Vous pouvez souvent contrôler quand des dépendances, telles que d’autres vues, marquent votre corps de vue comme obsolète, surtout si ce sont vos vues. Cependant, SwiftUI effectue également un travail supplémentaire afin d’afficher votre vue. Bien que ce travail soit nécessaire et généralement inévitable, il peut être utile de savoir quand il se produit. Les informations sur les causes et les effets des mises à jour qui sont fournies par le nouvel outil Instruments SwiftUI sont très utiles. Le graphique Cause et Effet enregistre toutes ces relations de cause à effet et les affiche sous la forme d’un graphique qui ressemble à ceci.

    Nous commençons par la mise à jour du corps de la vue que nous examinons. La mise à jour apparaît sous la forme d’un nœud, une icône l’identifie comme une mise à jour du corps de vue et un titre indique son type de vue.

    Une flèche pointe vers elle depuis un nœud représentant le changement d’état. La flèche est intitulée update car le changement d’état a entraîné la mise à jour de la vue. Des arêtes intitulées « Creation » indiquent ce qui a fait apparaître votre vue pour la première fois dans la hiérarchie des vues.

    Le nœud de changement d’état comporte un titre qui indique le nom de la variable d’état et le type de vue auquel elle est associée. Lorsque vous sélectionnez le changement d’état, une trace d’appel indique l’endroit où la valeur a été mise à jour.

    En continuant vers la gauche du graphique de cause et effet, vous voyez que le changement d’état est dû à un geste, comme un appui sur un bouton.

    Steven, que montre le graphique des causes pour l’app Landmarks ? Examinons le graphique Cause et Effet pour comprendre pourquoi toutes ces mises à jour supplémentaires du corps de vue ont eu lieu.

    Voici la vue du graphique Cause et Effet. Le nœud pour LandmarkListItemView.body est sélectionné. Les nœuds bleus du graphique représentent des parties de mon code ou des actions que j’ai effectuées en interagissant avec l’app. Le graphique montre la chaîne de causes et d’effets de gauche à droite.

    Le nœud « Gesture » représente mes pressions sur le bouton favori.

    Cela a entraîné la mise à jour du tableau des sites favoris, ce qui a provoqué plusieurs mises à jour du corps de LandmarkListItemView. C’est bien plus que ce à quoi je m’attendais.

    On dirait que le fait d’appuyer sur un seul bouton favori entraîne la mise à jour de nombreuses vues d’éléments à l’écran, pas seulement de celle que j’ai touchée. Revenons au code pour comprendre ce qui se passe.

    Je vais revenir à LandmarkListItemView. Pour vérifier si un site est marqué comme favori, j’appelle modelData.isFavorite et je transmets le site. ModelData est mon objet modèle de niveau général, qui utilise la macro @Observable pour que SwiftUI mette à jour ma vue lorsque ses propriétés changent. Je vais cliquer sur isFavorite tout en maintenant la touche Commande enfoncée pour accéder à cette fonction.

    Ici, j’accède au tableau favoritesCollection.landmarks pour vérifier si ce site fait partie des favoris. Cela oblige @Observable à établir une dépendance entre chaque vue d’élément et l’ensemble du tableau des favoris. Ainsi, chaque fois que j’ajoute un favori au tableau, le corps de chaque vue d’élément s’exécute, car le tableau a changé. Je vais vous montrer comment ça fonctionne.

    Voici quelques-uns de mes LandmarkListItemViews. Et voici ma classe ModelData avec favoritesCollection, qui garde la trace de mes sites préférés. Actuellement, mon unique favori est le site numéro deux. La classe ModelData dispose d’une fonction isFavorite. Chaque LandmarkListItemView appelle cette fonction pour déterminer si l’icône doit être mise en surbrillance ou non.

    La fonction isFavorite vérifie si la collection contient le site, et chaque vue affiche son propre bouton. Comme chaque vue a accédé au tableau des favoris, même indirectement, la macro @Observable a créé une dépendance pour chaque vue sur l’ensemble du tableau des favoris.

    Que se passe-t-il si je souhaite ajouter un nouveau favori en appuyant sur le bouton favori dans l’une de mes autres vues ? La vue appelle toggleFavorite, qui ajoute un nouveau site à mes favoris. Comme toutes mes LandmarkListItemViews dépendent de favoritesCollection, toutes les vues sont marquées comme obsolètes et leur corps s’exécute à nouveau.

    Cependant, ce n’est pas idéal, car la seule vue que j’ai réellement modifiée est la vue numéro trois. J’ai besoin que les dépendances de données de ma vue soient plus granulaires, afin que lorsque les données de mon app changent, seuls les corps de vue nécessaires soient mis à jour.

    Réfléchissons-y un peu. Je sais que chacune de mes vues présente un site qui a son propre statut de favori : favori ou non.

    Pour suivre ce statut, je vais créer un modèle de vue Observable pour ma vue. Le modèle a une propriété isFavorite pour suivre le statut de favori, et chaque vue aura son propre modèle de vue.

    Je peux stocker mes modèles de vue dans la classe ModelData. Chaque vue peut récupérer son modèle et activer ou désactiver le favori au besoin. Ainsi, au lieu que chaque vue dépende de l’ensemble complet des favoris, chacune dépend uniquement du modèle de vue de son propre site. Ajoutons donc un autre favori ! J’appuie sur le bouton, ce qui appelle toggleFavorite, qui met à jour le modèle de vue de la vue numéro un. Comme la vue numéro un dépend seulement de son propre modèle de vue, c’est la seule vue dont le corps s’exécute à nouveau. Voyons comment ces modifications se sont traduites dans Landmarks.

    Voici une trace que j’ai enregistrée après avoir implémenté les nouvelles améliorations du modèle de vue. Je clique à nouveau sur la sous-piste View Body Updates. Je sélectionne la même partie de la chronologie que précédemment.

    Dans le volet Détails, je vais développer le processus et le module Landmarks.

    Maintenant, il n’y a plus que deux mises à jour. Comme j’ai modifié deux favoris, cela semble correct, mais vérifions tout de même le graphique. Je survole le nom de la vue, clique sur la flèche et je choisis d’afficher le graphique de cause à effet.

    Et voici à nouveau le graphique.

    La flèche qui relie le nœud @Observable au corps de ma vue n’indique que deux mises à jour, une pour chaque bouton. En remplaçant la dépendance de chaque élément de vue à l’ensemble du tableau des favoris par un modèle de vue étroitement couplé, j’ai éliminé beaucoup de mises à jour inutiles du corps de vue, ce qui permet à mon app de mieux fonctionner. Dans cet exemple, le graphique était relativement petit, car les causes des mises à jour du corps de vue étaient très limitées. Cependant, le graphique peut devenir beaucoup plus grand s’il existe plusieurs causes distinctes. Cela peut se produire lorsqu’une vue lit à partir de l’environnement. Jed, peux-tu nous montrer un exemple ? Bien sûr ! Je vais d’abord expliquer comment fonctionne l’environnement. Les valeurs de l’environnement sont stockées dans la structure EnvironmentValues, un type de valeur similaire à un dictionnaire. Chacune de ces vues dépend de l’ensemble de la structure EnvironmentValues, car chaque vue accède à l’environnement à l’aide du wrapper de propriété environment. Lorsqu’une valeur de l’environnement est mise à jour, chaque vue dépendante de l’environnement est informée que son corps peut devoir s’exécuter. Ensuite, chacune de ces vues vérifie si la valeur qu’elle lit a changé. Si la valeur a changé, le corps de la vue doit s’exécuter à nouveau. Si elle n’a pas changé, SwiftUI peut ignorer l’exécution du corps de vue, car celle-ci est déjà à jour. Voyons comment ces mises à jour apparaissent dans le graphique Cause et effet.

    Le graphique comporte deux types principaux de nœuds représentant les mises à jour de l’environnement. Les mises à jour de l’environnement externe comprennent des éléments au niveau de l’app, tels que le schéma de couleurs, qui sont mis à jour en dehors de SwiftUI. Les mises à jour EnvironmentWriter représentent les modifications apportées à une valeur dans l’environnement qui se produisent dans SwiftUI. Les mises à jour effectuées dans votre app avec le modificateur dot-environment entrent dans cette catégorie. Supposons que la valeur de l’environnement du schéma de couleurs soit mise à jour car l’appareil est passé en mode sombre. À quoi cela ressemblerait-il dans le graphique Cause et effet pour ces vues ? Le graphique affichera un nœud Environnement externe pour View1, car le schéma de couleurs est une mise à jour de l’environnement au niveau du système. Le graphique affichera aussi un nœud indiquant que le corps de View1 a été exécuté. Comme View2 lit aussi l’environnement, elle comporte également une mise à jour de l’environnement externe dans le graphique qui est sa cause. Cependant View2 ne lit pas la valeur du schéma de couleurs, son corps ne s’exécute donc pas. Dans le graphique, une mise à jour de vue où le corps n’a pas été exécuté correspond à une icône grisée. Dans ce cas, ces deux nœuds d’environnement externe représentent la même mise à jour. Si vous survolez ou cliquez sur l’un des nœuds correspondant à la même mise à jour, ils seront tous deux mis en surbrillance en même temps afin de les identifier facilement. Ces deux mises à jour de vue apparaissent dans le graphique, car même dans les cas où le corps d’une vue n’a pas besoin d’être exécuté à la suite d’une mise à jour de l’environnement, il y a toujours un coût associé à la vérification des mises à jour de la valeur qui intéresse la vue. Le temps passé peut vite s’accumuler si votre app comporte de nombreuses vues qui lisent l’environnement. Il est donc important d’éviter de stocker dans l’environnement des valeurs qui sont très souvent mises à jour, comme des valeurs géométriques ou des minuteries. C’est donc le graphique Cause et effet. C’est un excellent moyen de visualiser le flux de données dans votre app, afin de vous assurer que vos vues ne se mettent pas à jour plus que nécessaire. Dans cette session, nous avons abordé des bonnes pratiques destinées à optimiser les performances de votre app SwiftUI. Il est important de veiller à la rapidité de vos corps de vue, afin que SwiftUI ait assez de temps pour afficher votre UI à l’écran sans retard. Les mises à jour inutiles du corps de vue peuvent s’accumuler. Concevez votre flux de données pour ne mettre à jour vos vues que lorsque cela est nécessaire, et faites attention avec les dépendances qui changent très fréquemment. N’oubliez pas d’utiliser Instruments dès le début et régulièrement pour analyser les performances de votre app durant son développement. Je sais que nous avons abordé de nombreux points aujourd’hui. Le plus important est de s’assurer que le contenu de vos vues se met à jour rapidement et uniquement si nécessaire pour garantir de bonnes performances avec SwiftUI. Utilisez Instruments SwiftUI pour contrôler les performances de votre app au fur et à mesure.

    Durant cette session, nous vous avons vu comment profiler vos apps avec Instruments SwiftUI, mais il y a encore beaucoup à découvrir. Consultez la documentation liée dans la description de la vidéo pour en savoir plus sur les autres fonctionnalités d’Instruments. Nous avons aussi ajouté des liens vers d’autres vidéos et documents de référence sur l’analyse et l’optimisation des performances de votre app. Merci de nous avoir suivis ! Nous avons hâte de voir comment vous allez optimiser vos apps grâce au nouvel outil Instruments SwiftUI.

    • 8:47 - LandmarkListItemView

      import SwiftUI
      import CoreLocation
      
      /// A view that shows a single landmark in a list.
      struct LandmarkListItemView: View {
          @Environment(ModelData.self) private var modelData
      
          let landmark: Landmark
      
          var body: some View {
              Image(landmark.thumbnailImageName)
                  .resizable()
                  .aspectRatio(contentMode: .fill)
                  .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
                  .overlay { ... }
                  .clipped()
                  .cornerRadius(Constants.cornerRadius)
                  .overlay(alignment: .bottom) {
                      VStack(spacing: 6) {
                          Text(landmark.name)
                              .font(.title3).fontWeight(.semibold)
                              .multilineTextAlignment(.center)
                              .foregroundColor(.white)
      
                          if let distance {
                              Text(distance)
                                  .font(.callout)
                                  .foregroundStyle(.white.opacity(0.9))
                                  .padding(.bottom)
                          }
                      }
                  }
                  .contextMenu { ... }
          }
      
          private var distance: String? {
              guard let currentLocation = modelData.locationFinder.currentLocation else { return nil }
              let distance = currentLocation.distance(from: landmark.clLocation)
      
              let numberFormatter = NumberFormatter()
              numberFormatter.numberStyle = .decimal
              numberFormatter.maximumFractionDigits = 0
      
              let formatter = MeasurementFormatter()
              formatter.locale = Locale.current
              formatter.unitStyle = .medium
              formatter.unitOptions = .naturalScale
              formatter.numberFormatter = numberFormatter
              return formatter.string(from: Measurement(value: distance, unit: UnitLength.meters))
          }
      }
    • 12:13 - LocationFinder Class with Cached Distance Strings

      import CoreLocation
      
      /// A class the app uses to find the current location.
      @Observable
      class LocationFinder: NSObject {
          var currentLocation: CLLocation?
          private let currentLocationManager: CLLocationManager = CLLocationManager()
      
          private let formatter: MeasurementFormatter
      
          override init() {
              // Format the numeric distance
              let numberFormatter = NumberFormatter()
              numberFormatter.numberStyle = .decimal
              numberFormatter.maximumFractionDigits = 0
      
              // Format the measurement based on the current locale
              let formatter = MeasurementFormatter()
              formatter.locale = Locale.current
              formatter.unitStyle = .medium
              formatter.unitOptions = .naturalScale
              formatter.numberFormatter = numberFormatter
              self.formatter = formatter
      
              super.init()
              
              currentLocationManager.desiredAccuracy = kCLLocationAccuracyKilometer
              currentLocationManager.delegate = self
          }
      
          // MARK: - Landmark Distance
      
          var landmarks: [Landmark] = [] {
              didSet {
                  updateDistances()
              }
          }
      
          private var distanceCache: [Landmark.ID: String] = [:]
      
          private func updateDistances() {
              guard let currentLocation else { return }
      
              // Populate the cache with each formatted distance string
              self.distanceCache = landmarks.reduce(into: [:]) { result, landmark in
                  let distance = self.formatter.string(
                      from: Measurement(
                          value: currentLocation.distance(from: landmark.clLocation),
                          unit: UnitLength.meters
                      )
                  )
                  result[landmark.id] = distance
              }
          }
      
          // Call this function from the view to access the cached value
          func distance(from landmark: Landmark) -> String? {
              distanceCache[landmark.id]
          }
      }
      
      extension LocationFinder: CLLocationManagerDelegate {
          func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
              switch currentLocationManager.authorizationStatus {
              case .authorizedWhenInUse, .authorizedAlways:
                  currentLocationManager.requestLocation()
              case .notDetermined:
                  currentLocationManager.requestWhenInUseAuthorization()
              default:
                  currentLocationManager.stopUpdatingLocation()
              }
          }
          
          func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
              print("Found a location.")
              currentLocation = locations.last
              // Update the distance strings when the location changes
              updateDistances() 
          }
          
          func locationManager(_ manager: CLLocationManager, didFailWithError error: any Error) {
              print("Received an error while trying to find a location: \(error.localizedDescription).")
              currentLocationManager.stopUpdatingLocation()
          }
      }
    • 16:51 - LandmarkListItemView with Favorite Button

      import SwiftUI
      import CoreLocation
      
      /// A view that shows a single landmark in a list.
      struct LandmarkListItemView: View {
          @Environment(ModelData.self) private var modelData
      
          let landmark: Landmark
      
          var body: some View {
              Image(landmark.thumbnailImageName)
                  .resizable()
                  .aspectRatio(contentMode: .fill)
                  .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
                  .overlay { ... }
                  .clipped()
                  .cornerRadius(Constants.cornerRadius)
                  .overlay(alignment: .bottom) { ... }
                  .contextMenu { ... }
                  .overlay(alignment: .topTrailing) {
                      let isFavorite = modelData.isFavorite(landmark)
                      Button {
                          modelData.toggleFavorite(landmark)
                      } label: {
                          Label {
                              Text(isFavorite ? "Remove Favorite" : "Add Favorite")
                          } icon: {
                              Image(systemName: "heart")
                                  .symbolVariant(isFavorite ? .fill : .none)
                                  .contentTransition(.symbolEffect)
                                  .font(.title)
                                  .foregroundStyle(.background)
                                  .shadow(color: .primary.opacity(0.25), radius: 2, x: 0, y: 0)
                          }
                      }
                      .labelStyle(.iconOnly)
                      .padding()
                  }
          }
      }
    • 17:20 - ModelData Class

      /// A structure that defines a collection of landmarks.
      @Observable
      class LandmarkCollection: Identifiable {
          // ...
          var landmarks: [Landmark] = []
          // ...
      }
      
      /// A class the app uses to store and manage model data.
      @Observable @MainActor
      class ModelData {
          // ...
          var favoritesCollection: LandmarkCollection!
          // ...
      
          func isFavorite(_ landmark: Landmark) -> Bool {
              var isFavorite: Bool = false
              
              if favoritesCollection.landmarks.firstIndex(of: landmark) != nil {
                  isFavorite = true
              }
              
              return isFavorite
          }
      
          func toggleFavorite(_ landmark: Landmark) {
              if isFavorite(landmark) {
                  removeFavorite(landmark)
              } else {
                  addFavorite(landmark)
              }
          }
      
          func addFavorite(_ landmark: Landmark) {
              favoritesCollection.landmarks.append(landmark)
          }
      
          func removeFavorite(_ landmark: Landmark) {
              if let landmarkIndex = favoritesCollection.landmarks.firstIndex(of: landmark) {
                  favoritesCollection.landmarks.remove(at: landmarkIndex)
              }
          }
          // ...
      }
    • 20:50 - OnOffView

      struct OnOffView: View {
          @State private var isOn = true
          var body: some View {
              Text(isOn ? "On" : "Off")
          }
      }
    • 29:21 - Favorites View Model Class

      @Observable class ViewModel {
          var isFavorite: Bool
          
          init(isFavorite: Bool = false) {
              self.isFavorite = isFavorite
          }
      }
    • 29:21 - ModelData Class with New ViewModel

      @Observable @MainActor
      class ModelData {
          // ...
          var favoritesCollection: LandmarkCollection!
          // ...
      
          @Observable class ViewModel {
              var isFavorite: Bool
              init(isFavorite: Bool = false) {
                  self.isFavorite = isFavorite
              }
          }
      
          // Don't observe this property because we only need to react to changes
          // to each view model individually, rather than the whole dictionary
          @ObservationIgnored private var viewModels: [Landmark.ID: ViewModel] = [:]
      
          private func viewModel(for landmark: Landmark) -> ViewModel {
              // Create a new view model for a landmark on first access
              if viewModels[landmark.id] == nil {
                  viewModels[landmark.id] = ViewModel()
              }
              return viewModels[landmark.id]!
          }
      
          func isFavorite(_ landmark: Landmark) -> Bool {
              // When a SwiftUI view, such as LandmarkListItemView, calls
              // `isFavorite` from its body, accessing `isFavorite` on the 
              // view model here establishes a direct dependency between
              // the view and the view model
              viewModel(for: landmark).isFavorite
          }
      
          func toggleFavorite(_ landmark: Landmark) {
              if isFavorite(landmark) {
                  removeFavorite(landmark)
              } else {
                  addFavorite(landmark)
              }
          }
      
          func addFavorite(_ landmark: Landmark) {
              favoritesCollection.landmarks.append(landmark)
              viewModel(for: landmark).isFavorite = true
          }
      
          func removeFavorite(_ landmark: Landmark) {
              if let landmarkIndex = favoritesCollection.landmarks.firstIndex(of: landmark) {
                  favoritesCollection.landmarks.remove(at: landmarkIndex)
              }
              viewModel(for: landmark).isFavorite = false
          }
          // ...
      }
    • 31:34 - Cause and effect: EnvironmentValues

      struct View1: View {
          @Environment(\.colorScheme)
          private var colorScheme
      
          var body: some View {
              Text(colorScheme == .dark
                      ? "Dark Mode"
                      : "Light Mode")
          }
      }
      
      struct View2: View {
          @Environment(\.counter) private var counter
      
          var body: some View {
              Text("\(counter)")
          }
      }
    • 0:00 - Introduction et ordre du jour
    • Apprenez à optimiser les performances de votre app SwiftUI avec le nouvel outil SwiftUI et le modèle intégré dans Instruments 26. Profitez-en pour profiler vos apps et détecter les ralentissements liés aux mises à jour longues ou inutiles, responsables de saccades, blocages et décalages. L’app exemple présentée, Landmarks, affiche des monuments mondiaux et leur distance par rapport à la position de l’utilisateur. Apprenez à utiliser le nouvel instrument SwiftUI pour fluidifier le défilement en identifiant et en corrigeant les problèmes de performance dans le code SwiftUI.

    • 2:19 - Découvrez l’instrument SwiftUI.
    • Instruments 26 introduit un nouvel instrument et modèle SwiftUI pour profiler les apps SwiftUI. Comme les instruments Time Profiler et Hangs and Hitches, il aide à identifier les problèmes de performance. La piste Update Groups affiche le travail SwiftUI. Les trois autres pistes signalent les longues mises à jour (en orange ou rouge selon leur probabilité de provoquer des saccades ou des blocages). Pour utiliser le nouvel instrument SwiftUI, installez Xcode 26 et mettez à jour le SE de votre appareil.

    • 4:20 - Diagnostiquer et corriger les mises à jour longues de la vue du corps
    • L’exemple utilise Xcode 26 et Instruments 26 pour profiler l’app Landmarks, développée en SwiftUI. Commencez par lancer Instruments et sélectionnez le modèle SwiftUI pour enregistrer les performances de l’app. Interagissez ensuite avec l’app sur iPhone en faisant défiler une liste des monuments, ce qui charge des vues supplémentaires. Après l’arrêt de l’enregistrement, Instruments traite les données, et vous pouvez analyser la piste SwiftUI. Analysez la piste Long View Body Updates pour repérer les vues, comme LandmarkListItemView, causant des problèmes de performance. En développant la piste SwiftUI et en utilisant l’instrument Time Profiler, vous pouvez approfondir l’analyse de l’utilisation CPU lors des mises à jour de vues. Vous pouvez constater que certaines propriétés calculées (notamment les formateurs pour convertir et afficher les distances) consomment trop de temps. Il est important d’optimiser le temps d’exécution du corps de vue dans SwiftUI, car celui-ci s’exécute sur le thread principal et tout retard peut faire manquer des images, ce qui provoque des saccades. Les saccades rendent les animations moins fluides et peuvent nuire à l’expérience utilisateur. Pour résoudre ces problèmes de performance dans l’exemple, calculez et mettez en cache la chaîne de distance à l’avance, au lieu de faire ces calculs lors de la mise à jour du corps de la vue, afin d’assurer une app plus fluide et réactive. Dans Xcode, un processus d’optimisation est mis en place dans la classe LocationFinder, qui gère les mises à jour de position. Auparavant, les chaînes de distance étaient calculées dans le corps de vue de LandmarkListItemView, ce qui rendait les mises à jour inefficaces. Pour y remédier, la logique a été déplacée dans la classe LocationFinder. Le système crée et stocke les formateurs dans l’initialiseur pour les réutiliser, évitant ainsi des créations redondantes. Un dictionnaire met en cache les chaînes de distance après le calcul. La fonction updateDistances est chargée de recalculer ces chaînes à chaque changement de position. Cette fonction utilise les formateurs créés pour générer la chaîne de distance et l’enregistrer dans le cache. Le framework CoreLocation appelle la méthode locationManager(_:didUpdateLocations:) sur son objet CLLocationManagerDelegate lorsque la position de l’appareil change. En appelant updateDistances dans cette méthode, le cache reste à jour. Les vues accèdent aux chaînes de distance mises en cache, évitant ainsi de les recalculer lors des mises à jour du corps de la vue. Ajoutez ensuite une nouvelle fonctionnalité : un bouton cœur pour vos monuments préférés. Lorsque quelqu’un appuie sur le bouton, la fonction toggleFavorite est appelée et met à jour le modèle pour ajouter ou retirer le monument de la liste des favoris. La vue reflète alors ce changement en affichant une icône de cœur pleine ou vide. En analysant la nouvelle fonctionnalité de favoris dans Instruments, vous constaterez peut-être que LandmarkListItemView se met à jour plus souvent que prévu. Ce comportement inattendu pousse à examiner la logique de mise à jour de la vue, soulignant les difficultés à déboguer les mises à jour dans les apps SwiftUI, contrairement aux apps UIKit, où l’inspection par points d’arrêt est plus directe qu’avec un framework déclaratif.

    • 19:54 - Comprendre les causes et les effets des mises à jour de SwiftUI
    • Dans Xcode, le débogage du code impératif, comme dans les apps UIKit, est simple grâce aux backtraces. Mais, cette approche est moins efficace avec SwiftUI en raison de sa nature déclarative. Le modèle de données de SwiftUI (AttributeGraph), gère les dépendances entre les vues pour optimiser les mises à jour. Lorsqu’une vue SwiftUI est déclarée, elle adopte le protocole View et définit son apparence et son comportement via la propriété body. La propriété body retourne une autre valeur View, et SwiftUI gère en interne l’état de la vue et ses mises à jour à l’aide d’attributs. Les changements d’état déclenchent des transactions marquant certains attributs comme obsolètes. SwiftUI met à jour la hiérarchie des vues lors de l’image suivante, en ne rafraîchissant que les vues nécessaires grâce à la chaîne de dépendances. Pour savoir pourquoi une vue SwiftUI s’est mise à jour, utilisez le nouveau graphe de causes et effets de l’instrument SwiftUI. Il visualise les liens entre les mises à jour, en montrant la chaîne des causes allant des interactions utilisateur, comme les gestes, jusqu’aux changements d’état et aux mises à jour du corps de vue. En examinant ce graphe, vous pouvez repérer des inefficacités, comme des mises à jour inutiles, et optimiser votre code. Dans l’app Landmarks, la classe ModelData contient une propriété favoritesCollection qui stocke les monuments ajoutés aux favoris dans un tableau. Au départ, chaque LandmarkListItemView vérifiait si un monument était en favori en accédant à l’ensemble du tableau favoritesCollection, créant ainsi une dépendance entre chaque vue et tout le tableau. Cela entraînait de mauvaises performances, car l’ajout d’un favori relançait le corps de chaque vue. Pour résoudre ce problème, l’approche a été repensée. Chaque monument dispose désormais d’un modèle Observable qui contient son statut de favori. Chaque LandmarkListItemView a désormais son propre modèle, sans dépendre du tableau des favoris. Grâce à ce changement, seule la vue concernée est mise à jour lorsqu’un favori est modifié. Cette optimisation améliore nettement les performances, comme le montre la baisse des mises à jour de vues dans le graphe de causes et effets. Ce graphe montre aussi comment des changements comme le thème de couleurs peuvent impacter les vues. Même si le corps d’une vue n’a pas besoin de s’exécuter après une mise à jour de l’environnement, vérifier ces changements a un coût : mieux vaut éviter d’y stocker des valeurs qui changent souvent.

    • 35:01 - Étapes suivantes
    • Le nouvel instrument SwiftUI d’Instruments 26 est accompagné de vidéos, de fonctions avancées et de ressources sur l’optimisation des performances dans la documentation développeur.

Developer Footer

  • Vidéos
  • WWDC25
  • Optimiser les performances de SwiftUI avec Instruments
  • 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