Dans le chapitre précédent, nous avons voulu tester notre application dans le simulateur, et nous avons obtenu cette erreur :
Et on ne va pas tirer beaucoup d'informations du message puisqu'on ne sait pas ce qu'est un thread. Ce chapitre va y remédier !
Découvrez le multi-threading
Pour comprendre ce qu'est un thread, il faut savoir comment fonctionne le processeur de votre téléphone.
Un processeur est une machine à calculer, mais contrairement à nous autres êtres pensants, le processeur ne fait qu'une seule chose à la fois.
Le problème c'est que parfois, vous avez besoin qu'il fasse deux, voire plusieurs choses à la fois, surtout avec un Smartphone réputé pour être multitâche !
Par exemple, lorsque vous vous déplacez dans une carte, l'iPhone doit gérer en même temps l'interprétation de votre geste sur l'écran tactile, le chargement de nouvelles zones de la carte sur le réseau, et la modification de l'affichage à l'écran à partir des données de votre mouvement et de celles des zones chargées.
Pour y arriver et que le résultat soit fluide pour l'utilisateur, le processeur utilise la technique du multi-threading : il va séparer son activité en plusieurs threads et il va passer très rapidement de l'un à l'autre en fonction de ses besoins, jusqu'à ce qu'il ait terminé de tout traiter. Cela donne quelque chose comme ça :
On voit sur ce schéma le processeur passer rapidement d'un thread à l'autre.
Mais il y a encore plus efficace ! La dernière fois que vous avez acheté un ordinateur, vous avez peut-être entendu parler des processeurs bicœurs ou quadricœurs. Ce sont des processeurs qui, eux, ont vraiment la capacité de faire deux ou quatre choses à la fois, car ils ont deux ou quatre centres de calculs.
L'intérêt d'avoir plusieurs cœurs, c'est que du coup les calculs ont vraiment lieu en parallèle. Et le schéma précédent devient :
Les threads prennent tout leur sens avec ce genre de puce, car ils avancent en même temps, et du coup les calculs sont bien plus rapides !
Appréhendez la logique des queues et du GCD
Les threads sont un concept très bas niveau, c'est-à-dire très proche de la réalité matérielle d'un processeur. Du coup, les développeurs ont créé une logique un peu plus abstraite, mais plus facile à manipuler : les queues.
Le mécanisme qui permet de s'affranchir des threads en tant que développeur s'appelle Grand Central Dispatch. C'est un peu comme une gare de triage. En tant que développeur, on va pouvoir créer des lignes de chemin de fer, les unes à côté des autres, c'est ce qu'on appelle des queues. Et on va pouvoir rajouter des trains sur ces queues, c'est ce qu'on appelle des tâches.
Grand Central Dispatch (GCD) est la technologie qui va s'occuper de faire avancer les trains en fonction des ressources du processeur, en minimisant l'impact sur la batterie du téléphone, et selon un ordre de priorité défini par le développeur.
Il y a trois types de queues :
Main Queue : c'est la queue principale, la plus prioritaire. Elle existe toujours. On va y revenir ;
Global Queues : ce sont des queues standard du système qui peuvent être utilisées par le développeur. Elles ont 4 niveaux de priorité. Les tâches dans les queues les plus prioritaires seront exécutées plus vite :
User-interactive
: très prioritaire. À utiliser pour tout ce qui est lié à l'interface pour obtenir une expérience utilisateur fluide,User-initiated
: pour les tâches qui sont lancées par l'utilisateur, mais pour lesquelles on peut attendre une réponse,Utility
: pour les longues tâches dont l'utilisateur suit la progression : traitement d'images, téléchargement de ressources, etc.,Background
: pour les tâches de fond dont l'utilisateur n'est pas au courant, et qui ne sont donc pas urgentes ;
Custom Queue : ce sont des queues complètement paramétrables par le développeur.
Gérez les queues dans URLSession
OK, mais concrètement qu'est-ce que ça donne ?
Je sais, c'est beaucoup à digérer. Raccrochons les wagons et revenons au problème qui nous occupe.
Par défaut, tout le code que vous rédigez s'exécute dans la Main Queue (par habitude, certains parlent aussi de Main Thread, mais faites attention, ce ne sont pas des synonymes).
Le problème, c'est que lorsque vous faites un appel réseau dans la Main Queue, il faut attendre le retour et parfois cela prend du temps. Mais du coup, tout le reste de votre application est bloqué, interface comprise !
Pour empêcher cela, URLSessionTask
effectue ses tâches de façon asynchrone dans une queue séparée. Et quand la réponse revient, je suis toujours dans une queue séparée. Pour être très concret, voici ce qui se passe dans le code :
static func getQuote(callback: @escaping (Bool, Quote?) -> Void) {
// On est dans la Main Queue par défaut
let request = createQuoteRequest()
let session = URLSession(configuration: .default)
// Pour éviter de bloquer l'application, la tâche va avoir lieu dans une queue séparée.
let task = session.dataTask(with: request) { (data, response, error) in
// À la réception de la réponse je suis toujours dans la queue séparée
}
task.resume()
}
Et c'est là qu'est le problème ! Car il existe une règle d'or avec les threads en iOS (et si vous ne retenez qu'une seule chose de ce chapitre, retenez ça !) :
Tout ce qui touche à l'interface doit avoir lieu dans la Main Queue !
Or, ici quand on reçoit la réponse de notre appel réseau, on est dans une queue séparée, et tout ce qu'on fait ensuite a lieu dans une queue séparée, que ce soit l'envoi du callback ou la mise à jour de l'interface.
Et maintenant, on comprend mieux ce que nous dit Xcode :
Main Thread Checker: UI API called on a background thread: -[UILabel setText:]
Il ne veut pas effectuer la méthode setText
de UILabel
, car on n'est pas dans la Main Queue
.
Qu'à cela ne tienne, on va y retourner !
Gérez les queues avec DispatchQueue
Pour gérer les queues avec Swift, on utilise la classe DispatchQueue
. Elle permet de créer des queues Custom, Global, ou de revenir dans la Main Queue. Pour revenir dans la Main Queue, on écrit :
DispatchQueue.main.async {
// Le code qui a lieu s'exécute dans la Main Queue
}
Il ne nous reste plus qu'à rajouter ça dans nos deux appels, ce qui donne ceci pour la fonction getQuote
:
static func getQuote(callback: @escaping (Bool, Quote?) -> Void) {
let request = createQuoteRequest()
let session = URLSession(configuration: .default)
let task = session.dataTask(with: request) { (data, response, error) in
DispatchQueue.main.async {
// (...)
}
task.resume()
}
et pour la fonction getImage
:
private static func getImage(completionHandler: @escaping ((Data?) -> Void)) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: pictureUrl) { (data, response, error) in
DispatchQueue.main.async {
// (...)
}
}
task.resume()
}
Vous pouvez lancer votre application dans le simulateur et ça y est ! Tout fonctionne ! Bravo !
En résumé
Les threads permettent au processeur de faire plusieurs choses à la fois en parallélisant ses calculs.
Les queues sont des objets qui permettent au développeur de répartir des tâches sur plusieurs files différentes, notamment dans le but de ne pas bloquer l'application lorsqu'une tâche est trop longue.
Il existe plusieurs types de queues.
Grand Central Dispatch
est le nom de la technologie qui gère l'avancement des tâches sur les différentes queues.Tout ce qui touche à l'interface doit avoir lieu dans la Main Queue !
Pour revenir dans la Main Queue, on fait la commande suivante :
DispatchQueue.main.async {
// Le code ici s'exécute dans la Main Queue
}
Bravo, vous savez désormais gérer les queues. Dans le prochain chapitre, nous allons améliorer notre code pour gérer le cas des requêtes concurrentes.