• 12 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 24/05/2022

Utilisez le résultat de la requête

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  et  author  de type  String  ;

  • imageData  de type  Data  .

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.

Illustration du dialogue entre modèle et vue via le contrôleur. À gauche, le modèle, à droite la vue, au milieu le contrôleur. Une flèche “Outlet” part du contrôleur vers la vue. Une flèche “Propriété” part du contrôleur vers le modèl

Mais il y a d'autres mécanismes et dans ce cours, nous allons parler d'un deuxième mécanisme : les callbacks.

Illustration du mécanisme de callback. Ajout d’une flèche “Callback” qui part du contrôleur vers le modèle, et retourne vers le contrôleur.

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 objet  Quote  ;

    • gérez le retour des requêtes en informant le contrôleur si une erreur est survenue ;

    • appelez la méthode  getQuote  depuis la fonction  tappedNewQuoteButton  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.

Exemple de certificat de réussite
Exemple de certificat de réussite