View in English

  • Apple Developer
    • Get Started

    Explore Get Started

    • Overview
    • Learn
    • Apple Developer Program

    Stay Updated

    • Latest News
    • Hello Developer
    • Platforms

    Explore Platforms

    • Apple Platforms
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    Featured

    • Design
    • Distribution
    • Games
    • Accessories
    • Web
    • Home
    • CarPlay
    • Technologies

    Explore Technologies

    • Overview
    • Xcode
    • Swift
    • SwiftUI

    Featured

    • Accessibility
    • App Intents
    • Apple Intelligence
    • Games
    • Machine Learning & AI
    • Security
    • Xcode Cloud
    • Community

    Explore Community

    • Overview
    • Meet with Apple events
    • Community-driven events
    • Developer Forums
    • Open Source

    Featured

    • WWDC
    • Swift Student Challenge
    • Developer Stories
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Centers
    • Documentation

    Explore Documentation

    • Documentation Library
    • Technology Overviews
    • Sample Code
    • Human Interface Guidelines
    • Videos

    Release Notes

    • Featured Updates
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • Downloads

    Explore Downloads

    • All Downloads
    • Operating Systems
    • Applications
    • Design Resources

    Featured

    • Xcode
    • TestFlight
    • Fonts
    • SF Symbols
    • Icon Composer
    • Support

    Explore Support

    • Overview
    • Help Guides
    • Developer Forums
    • Feedback Assistant
    • Contact Us

    Featured

    • Account Help
    • App Review Guidelines
    • App Store Connect Help
    • Upcoming Requirements
    • Agreements and Guidelines
    • System Status
  • Quick Links

    • Events
    • News
    • Forums
    • Sample Code
    • Videos
 

Vidéos

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

Plus de vidéos

  • À propos
  • Résumé
  • 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

    • Performance and metrics
    • Measuring your app’s power use with Power Profiler
    • Understanding and improving SwiftUI performance
    • Analyzing the performance of your visionOS app
    • Improving app responsiveness
      • 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…
    • 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
    • App Store
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • Icon Composer
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • Apple Intelligence
    • Audio & Video
    • Augmented Reality
    • Business
    • Design
    • Distribution
    • Education
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning & AI
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Downloads
    • Sample Code
    • Videos
    Open Menu Close Menu
    • Help Guides & Articles
    • Contact Us
    • Forums
    • Feedback & 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
    • Mini Apps Partner 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
    Read the latest news.
    Get the Apple Developer app.
    Copyright © 2026 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines