Nos appels réseaux fonctionnent et nous permettent de récupérer en une fois une nouvelle citation et une nouvelle photo.
Nous n'avons plus qu'à utiliser ces données dans le contrôleur pour les afficher à l'écran.
Créez le modèle Quote
Pour manipuler des données, il faut être structuré. On ne va pas juste envoyer des String
et des Data
comme ça au contrôleur. À la place, on va envoyer un joli petit objet Quote
.
Je vous laisse donc créer un fichier Quote.swift
. Il contiendra une structure Quote
qui admet 3 propriétés :
text
etauthor
de typeString
;imageData
de typeData
.
Vous pouvez télécharger la correction ici.
Appréhendez le fonctionnement des callbacks
Souvenez-vous, en MVC, le modèle discute avec le contrôleur via les notifications.
Mais il y a d'autres mécanismes et dans ce cours, nous allons parler d'un deuxième mécanisme : les callbacks.
Les callbacks sont très simples. Le contrôleur va appeler une fonction du modèle, et il va indiquer, en paramètre de la fonction, l'action à effectuer lorsque la réponse est reçue. Et pour cela, nous allons simplement utiliser les fermetures.
Nous allons commencer par modifier la déclaration de notre fonction getQuote
en ajoutant le paramètre callback
. Ce paramètre est une fermeture, et est donc du type fonction.
Pour définir précisément son type, il faut réfléchir à ce que l'on veut que le callback renvoie :
un booléan
success
qui permet de savoir si l'appel a réussi ou non ;un objet
quote
qui est l'objet que nous avons récupéré et construit avec nos requêtes.
Cela donne la déclaration suivante pour getQuote :
static func getQuote(callback: @escaping (Bool, Quote?) -> Void) { (...) }
On utilise en paramètre de notre fermeture un booléan et un objet Quote
optionnel car si la requête échoue, on n'a pas d'objet à renvoyer.
Envoyez le callback
Succès
Nous allons maintenant envoyer le callback. Et pour cela, nous allons commencer par créer notre objet quote à partir des trois données reçues : author
, text
et data
.
let quote = Quote(text: text, author: author, imageData: data)
Ensuite, nous allons envoyer le callback comme ceci :
callback(true, quote)
On passe le booléen à true car on est dans le cas où la requête a réussi ; ensuite, on passe l'objet quote que nous venons de créer.
Erreur
Voyons maintenant les cas d'erreur. Si on regarde notre fonction getQuote
, on ne gère pas du tout les erreurs pour l'instant :
let task = session.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
return
}
guard let responseJSON = try? JSONDecoder().decode([String: String].self, from: data), let text = responseJSON["quoteText"],
let author = responseJSON["quoteAuthor"] else {
return
}
getImage { (data) in
guard let data = data else {
return
}
let quote = Quote(text: text, author: author, imageData: data)
callback(true, quote)
}
}
On fait plein de vérifications, mais si ça ne se passe pas comme prévu, on ne fait rien ! On va pouvoir utiliser un peu plus notre guard . Si ça ne se passe pas comme prévu, on va envoyer un callback d'échec :
callback(false, nil)
Voici ce que ça donne :
let task = session.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
callback(false, nil)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
callback(false, nil)
return
}
guard let responseJSON = try? JSONDecoder().decode([String: String].self, from: data), let text = responseJSON["quoteText"],
let author = responseJSON["quoteAuthor"] else {
callback(false, nil)
return
}
getImage { (data) in
guard let data = data else {
return
}
let quote = Quote(text: text, author: author, imageData: data)
callback(true, quote)
}
}
Il nous reste maintenant à faire l'équivalent dans la méthode getImage
. En effet, on ne gère pas plus les erreurs dans celle-ci :
let task = session.dataTask(with: pictureUrl) { (data, response, error) in
guard let data = data, error == nil else {
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
return
}
completionHandler(data)
}
De la même manière, on va rajouter dans notre guard
le completionHandler
comme ceci :
let task = session.dataTask(with: pictureUrl) { (data, response, error) in
guard let data = data, error == nil else {
completionHandler(nil)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
completionHandler(nil)
return
}
completionHandler(data)
}
Si le téléchargement échoue, on ne peut pas renvoyer de données et donc à la place, on renvoie nil
. Cette information est utilisée ensuite dans la fonction changeQuote
pour envoyer les bons paramètres dans le callback.
Réceptionnez les callbacks dans le contrôleur
Nos callbacks sont envoyés, et maintenant il faut les réceptionner dans le contrôleur.
Nous allons commencer par appeler la méthode getQuote
dans notre fonction tappedNewQuoteButton
:
@IBAction func tappedNewQuoteButton() {
QuoteService.getQuote { (success, quote) in
}
}
Ensuite, nous allons vérifier que le paramètre success
est à true
et que l'objet quote
ne vaut pas nil
.
QuoteService.getQuote { (success, quote) in
guard let quote = quote, success == true else {
// Présenter un message d'erreur
return
}
// Afficher la citation
}
Si tout va bien, on affiche la citation, sinon on présente une alerte à l'utilisateur.
Essayez de le faire par vous-même d'abord ! Voici ma version:
@IBAction func tappedNewQuoteButton() {
QuoteService.getQuote { (success, quote) in
guard let quote = quote, success == true else {
self.presentAlert()
return
}
self.update(quote: quote)
}
}
private func update(quote: Quote) {
quoteLabel.text = quote.text
authorLabel.text = quote.author
imageView.image = UIImage(data: quote.imageData)
}
private func presentAlert() {
let alertVC = UIAlertController(title: "Error", message: "The quote download failed.", preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
present(alertVC, animated: true, completion: nil)
}
J'ai factorisé chaque cas dans deux fonctions, le reste ne devrait pas poser problème.
Tadaaa...
Et voilà ! Il n'y a plus qu'à tester ! Vous pouvez lancer le simulateur et appuyer sur le bouton New Quote.
Et.... c'est le drame ! Ça ne marche pas ! À la place, vous avez un message d'erreur dans la console qui vous dit :
Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.
Alors oui je sais, après tous ces efforts, c'est frustrant de finir sur une erreur ! Mais ne vous inquiétez pas, on va résoudre tout ça dès le prochain chapitre en parlant du concept de thread !
En résumé
Pour utiliser le résultat des requêtes et les transmettre au controller :
créez une structure
Quote
qui contient 3 variables :text
,author
,imageData
;
ajoutez un callback à la méthode
getQuote
qui renvoie un booléen de succès et un objetQuote
;gérez le retour des requêtes en informant le contrôleur si une erreur est survenue ;
appelez la méthode
getQuote
depuis la fonctiontappedNewQuoteButton
du contrôleur ;gérez le retour de la méthode.
Et voilà, vous savez désormais réaliser des appels réseaux simples ! Suivez-moi dans la deuxième partie de ce cours pour professionnaliser vos requêtes. Mais avant, je vous propose de tester vos connaissances grâce au quiz.