Introduction à la concurrence et au multithreading en Swift
Les applications iOS et macOS modernes nécessitent de plus en plus l'exécution de plusieurs tâches simultanément. Qu'il s'agisse de télécharger des données depuis le réseau, de traiter des images ou d'effectuer des calculs mathématiques complexes — une gestion efficace des threads est indispensable. En Swift, la concurrence et le multithreading sont implémentés au plus haut niveau, offrant au développeur des outils puissants et sécurisés.
Dans cet article, nous allons examiner trois approches principales : Grand Central Dispatch (GCD), OperationQueue et le mot-clé moderne async/await, apparu dans Swift 5.5. Vous apprendrez à choisir correctement entre elles et à éviter les erreurs typiques telles que les conditions de course (race condition) et les blocages mutuels (deadlock).
Grand Central Dispatch (GCD) — le classique du multithreading en Swift
Grand Central Dispatch (GCD) est une bibliothèque bas niveau d'Apple qui gère un pool de threads. Le développeur n'a pas besoin de créer et de détruire les threads manuellement — GCD le fait automatiquement, en distribuant les tâches sur les cœurs disponibles du processeur.
Les files d'attente principales de GCD
Dans GCD, il existe deux types de files d'attente : séquentielles (serial) et parallèles (concurrent). Les tâches dans une file séquentielle sont exécutées les unes après les autres, tandis que dans une file parallèle, elles peuvent être lancées simultanément.
// Création d'une file séquentielle
let serialQueue = DispatchQueue(label: "com.example.serial")
serialQueue.async {
print("Tâche 1")
}
serialQueue.async {
print("Tâche 2")
}
// Création d'une file parallèle
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)
concurrentQueue.async {
print("Tâche parallèle A")
}
concurrentQueue.async {
print("Tâche parallèle B")
}
Files globales et QoS
GCD fournit des files globales prêtes à l'emploi avec différentes priorités (Quality of Service). Utilisez-les lorsque vous n'avez pas besoin de créer votre propre file :
.userInteractive— pour les mises à jour de l'interface utilisateur (priorité la plus élevée).userInitiated— pour les tâches initiées par l'utilisateur.utility— pour les opérations d'arrière-plan longues.background— pour les tâches non critiques en termes de temps
DispatchQueue.global(qos: .userInitiated).async {
// Travail lourd
let result = performComplexCalculation()
DispatchQueue.main.async {
// Retour sur le thread principal pour mettre à jour l'UI
self.updateUI(with: result)
}
}
Important : Mettez toujours à jour l'interface utilisateur uniquement sur le thread principal (DispatchQueue.main). Ignorer cette règle entraîne des plantages imprévisibles de l'application.
OperationQueue — gestion des dépendances entre tâches
Si GCD est un « couteau suisse », OperationQueue est un planificateur complet avec possibilité d'annulation, de priorisation et de création de dépendances entre les opérations. Chaque opération est une instance de la classe Operation (ou de sa sous-classe BlockOperation).
Création et exécution des opérations
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 2 // Limite le nombre d'opérations parallèles
let operation1 = BlockOperation {
print("Opération 1")
}
let operation2 = BlockOperation {
print("Opération 2")
}
// Ajout d'une dépendance : operation2 commencera seulement après la fin de operation1
operation2.addDependency(operation1)
operationQueue.addOperations([operation1, operation2], waitUntilFinished: false)
Annulation et priorités
Contrairement à GCD, OperationQueue permet d'annuler une opération spécifique ou toute la file :
// Annulation d'une opération
operation1.cancel()
// Annulation de toute la file
operationQueue.cancelAllOperations()
// Définition de la priorité
operation1.