-
Découvrir la simultanéité dans SwiftUI
Découvrez comment SwiftUI exploite la simultanéité Swift pour créer des apps sûres et réactives. Découvrez comment SwiftUI utilise le main actor par défaut et décharge le travail à d'autres acteurs. Apprenez à interpréter les annotations de simultanéité et à gérer les tâches asynchrones avec la boucle d'évènements de SwiftUI pour des animations fluides et des mises à jour de l'interface utilisateur. Vous repartirez en sachant comment éviter les concurrences de données et écrire du code sans crainte.
Chapitres
- 0:00 - Introduction
- 2:13 - Le main actor Meadows
- 7:17 - Falaises de concurrence
- 16:53 - Code Camp
- 23:47 - Étapes suivantes
Ressources
- Mutex
- Concurrency
- Updating an App to Use Swift Concurrency
- The Swift Programming Language: Concurrency
Vidéos connexes
WWDC25
WWDC23
-
Rechercher dans cette vidéo…
-
-
2:45 - UI for extracting colors
// UI for extracting colors struct ColorScheme: Identifiable, Hashable { var id = UUID() let imageName: String var colors: [Color] } @Observable final class ColorExtractor { var imageName: String var scheme: ColorScheme? var isExtracting: Bool = false var colorCount: Float = 5 func extractColorScheme() async {} } struct ColorExtractorView: View { @State private var model = ColorExtractor() var body: some View { ImageView( imageName: model.imageName, isLoading: model.isExtracting ) EqualWidthVStack { ColorSchemeView( isLoading: model.isExtracting, colorScheme: model.scheme, extractCount: Int(model.colorCount) ) .onTapGesture { guard !model.isExtracting else { return } withAnimation { model.isExtracting = true } Task { await model.extractColorScheme() withAnimation { model.isExtracting = false } } } Slider(value: $model.colorCount, in: 3...10, step: 1) .disabled(model.isExtracting) } } } } -
5:55 - AppKit and UIKit require @MainActor: an example
// AppKit and UIKit require @MainActor // Example: UIViewRepresentable struct FancyUILabel: UIViewRepresentable { func makeUIView(context: Context) -> UILabel { let label = UILabel() // customize the label... return label } } -
6:42 - UI for extracting colors
// UI for extracting colors struct ColorScheme: Identifiable, Hashable { var id = UUID() let imageName: String var colors: [Color] } @Observable final class ColorExtractor { var imageName: String var scheme: ColorScheme? var isExtracting: Bool = false var colorCount: Float = 5 func extractColorScheme() async {} } struct ColorExtractorView: View { @State private var model = ColorExtractorModel() var body: some View { ImageView( imageName: model.imageName, isLoading: model.isExtracting ) EqualWidthVStack(spacing: 30) { ColorSchemeView( isLoading: model.isExtracting, colorScheme: model.scheme, extractCount: Int(model.colorCount) ) .onTapGesture { guard !model.isExtracting else { return } withAnimation { model.isExtracting = true } Task { await model.extractColorScheme() withAnimation { model.isExtracting = false } } } Slider(value: $model.colorCount, in: 3...10, step: 1) .disabled(model.isExtracting) } } } } -
8:26 - Animated circle, part of color scheme view
// Part of color scheme view struct SchemeContentView: View { let isLoading: Bool @State private var pulse: Bool = false var body: some View { ZStack { // Color wheel … Circle() .scaleEffect(isLoading ? 1.5 : 1) VStack { Text(isLoading ? "Please wait" : "Extract") if !isLoading { Text("^[\(extractCount) color](inflect: true)") } } .visualEffect { [pulse] content, _ in content .blur(radius: pulse ? 2 : 0) } .onChange(of: isLoading) { _, newValue in withAnimation(newValue ? kPulseAnimation : nil) { pulse = newValue } } } } } -
13:10 - UI for extracting colors
// UI for extracting colors struct ColorExtractorView: View { @State private var model = ColorExtractor() var body: some View { ImageView( imageName: model.imageName, isLoading: model.isExtracting ) EqualWidthVStack { ColorSchemeView( isLoading: model.isExtracting, colorScheme: model.scheme, extractCount: Int(model.colorCount) ) .onTapGesture { guard !model.isExtracting else { return } withAnimation { model.isExtracting = true } Task { await model.extractColorScheme() withAnimation { model.isExtracting = false } } } Slider(value: $model.colorCount, in: 3...10, step: 1) .disabled(model.isExtracting) } } } } -
13:47 - Part of color scheme view
// Part of color scheme view struct SchemeContentView: View { let isLoading: Bool @State private var pulse: Bool = false var body: some View { ZStack { // Color wheel … Circle() .scaleEffect(isLoading ? 1.5 : 1) VStack { Text(isLoading ? "Please wait" : "Extract") if !isLoading { Text("^[\(extractCount) color](inflect: true)") } } .visualEffect { [pulse] content, _ in content .blur(radius: pulse ? 2 : 0) } .onChange(of: isLoading) { _, newValue in withAnimation(newValue ? kPulseAnimation : nil) { pulse = newValue } } } } } -
17:42 - UI for extracting colors
// UI for extracting colors struct ColorExtractorView: View { @State private var model = ColorExtractor() var body: some View { ImageView( imageName: model.imageName, isLoading: model.isExtracting ) EqualWidthVStack { ColorSchemeView( isLoading: model.isExtracting, colorScheme: model.scheme, extractCount: Int(model.colorCount) ) .onTapGesture { guard !model.isExtracting else { return } withAnimation { model.isExtracting = true } Task { await model.extractColorScheme() withAnimation { model.isExtracting = false } } } Slider(value: $model.colorCount, in: 3...10, step: 1) .disabled(model.isExtracting) } } } } -
18:55 - Animate colors as they appear by scrolling
// Animate colors as they appear by scrolling struct SchemeHistoryItemView: View { let scheme: ColorScheme @State private var isShown: Bool = false var body: some View { HStack(spacing: 0) { ForEach(scheme.colors) { color in color .offset(x: 0, y: isShown ? 0 : 60) } } .onScrollVisibilityChange(threshold: 0.9) { guard !isShown else { return } withAnimation { isShown = $0 } } } }
-
-
- 0:00 - Introduction
SwiftUI utilise la simultanéité Swift pour aider les développeurs à créer des apps rapides sans conflit de données. Swift 6.2 inclut un nouveau mode de langage qui ajoute implicitement l’annotation @MainActor à tous les types d’un module. SwiftUI exécute le code simultanément de différentes manières et fournit des annotations de simultanéité dans ses API pour aider les développeurs à identifier et à gérer la simultanéité. Cette séance s’attache à comprendre comment SwiftUI gère la simultanéité afin d’éviter les conflits de données et d’améliorer les performances des apps.
- 2:13 - Le main actor Meadows
Le protocole View de SwiftUI est isolé sur @MainActor, ce qui en fait la valeur par défaut du temps de compilation pour le code de l’interface utilisateur. Cela signifie que la majeure partie du code de l’interface utilisateur s’exécute implicitement sur le thread principal, ce qui simplifie le développement et garantit la compatibilité avec UIKit et AppKit. Les modèles de données instanciés dans un protocole View sont également automatiquement isolés. Les annotations @MainActor de SwiftUI reflètent son comportement au moment de l’exécution et sa sémantique prévue, pas seulement les aspects du temps de compilation.
- 7:17 - Falaises de concurrence
SwiftUI utilise des threads d’arrière-plan pour les tâches demandant beaucoup de ressources de calcul, comme les animations et les calculs de formes (par exemple, la méthode « path » du protocole « Shape », la fermeture « visualEffect », le protocole « Layout », la fermeture « onGeometryChange ») afin d’éviter les problèmes d’interface utilisateur. L’annotation « Sendable » signale les conditions potentielles de conflit de données lors du partage de données entre l’acteur principal et les threads d’arrière-plan. Pour éviter ces conflits, minimisez le partage des données. Lorsque le partage est nécessaire, faites des copies des données.
- 16:53 - Code Camp
Les rappels d’actions de SwiftUI sont conçus de manière synchrone pour garantir les mises à jour immédiates de l’interface utilisateur, en particulier pour les animations et les états de chargement. Les tâches de longue durée doivent être lancées de manière asynchrone, mais les mises à jour de l’interface utilisateur doivent rester synchrones. Séparez la logique de l’interface utilisateur de la logique autre (asynchrone), en utilisant l’état comme pont pour déclencher les mises à jour de l’interface utilisateur une fois les tâches asynchrones terminées. Faites en sorte que le code asynchrone de la vue reste simple et attachez-vous à informer le modèle des évènements d’interface utilisateur. La logique temporelle nécessite que les entrées et sorties de SwiftUI soient synchrones.
- 23:47 - Étapes suivantes
Swift 6.2 inclut un paramètre très utile d’isolement des acteurs par défaut. Si vous avez déjà une app, testez-le. Vous pourrez supprimer la plupart de vos annotations @MainActor. Mutex est un outil clé pour rendre une classe « sendable ». Consultez sa documentation officielle pour en savoir plus. Mettez-vous au défi d’écrire des tests unitaires pour le code asynchrone de votre app. Essayez de le faire sans importer SwiftUI.