Développer des Apps iOS 8 avec Swift

Partie 3 : Les bonnes pratiques

Jameson Quave

Traducteur

Mishulyna

Cette section a été entièrement mise à jour pour refléter les changements dans Xcode 6.3, à partir du 16 avril 2015.

Dans les deux premières parties, nous avons passé en revue quelques notions de base de Swift et mis en place un projet simple qui crée une Table View et y met quelques résultats obtenus suite à une recherche sur iTunes. Si vous ne les avez pas encore lues, consultez la partie 1 et la partie 2.

Les commentaires et les suggestions d'amélioration sont les bienvenus, alors, après votre lecture, n'hésitez pas. Commentez .

Retrouvez toute la série « Développer des Apps iOS 8 avec Swift »

Aller à la partie 2 maintenant ->

- Titre : Développer des Apps iOS 8 avec Swift

- Auteur : Jameson Quave

- Traducteur : Mishulyna

- Parution : 18 septembre 2014

- Mise à jour : 4 janvier 2016

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 Jameson Quave. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Voir également :

- Partie 1 : introduction - application basique Hello World

- Partie 2 : affichage des résultats d'une requête

- Partie 4 : ajouter les interactions

- Partie 5 : chargement asynchrone d'images et mise en cache

- Partie 6 : interaction avec des vues multiples

- Partie 7 : animations, audio et cellules Table View personnalisées

- Apprendre Swift à partir d'Objective-C

- Comprendre les optionnelles : quand et comment les utiliser ?

Sommaire

- I. Introduction

- II. Organiser le code

- III. Définir un protocole d'API

- IV. Adhérer au protocole

- V. Utiliser le protocole

- VI. OUF !

- VII. Code source

- VIII. Vous avez une question ou un problème ?

- IX. Remerciements Developpez

I - Introduction

Dans les deux premières parties, nous avons passé en revue quelques notions de base de Swift et mis en place un projet simple qui crée une Table View et y met quelques résultats obtenus suite à une recherche sur iTunes. Si vous ne les avez pas encore lues, consultez la partie 1 et la partie 2. Si vous aimez ces tutoriels, sachez que je travaille aussi sur un livre plein de contenu de qualité pour les développeurs Swift, et il est disponible en précommande dès maintenant.

Dans cette section, nous allons marquer un arrêt et nettoyer une partie du code que nous avons créé jusqu'à présent, en supprimant la logique réseau de notre contrôleur de vue et régler d'autres problèmes qui nuisent à la performance. Cela peut ne pas être la partie la plus fascinante de l'écriture d'une nouvelle application, mais c'est important ! Faisons-le.

II - Organiser le code

Tout d'abord, changeons le nom de View Controller pour le rendre un peu plus parlant. Ouvrez votre fichier ViewController.swift et remplacez toutes les références au ViewController avec le nouveau nom, SearchResultsViewController. Renommez également le fichier SearchResultsViewController.swift.

Si vous essayez d'exécuter l'application à ce moment, il y aura un plantage. C'est parce que notre fichier storyboard n'a pas été mis à jour pour renseigner la nouvelle classe ! Donc, ouvrez Main.storyboard, sélectionnez votre « View Controller » dans la scène (la navigation de côté gauche), puis sélectionnez l'Identity Inspector (à droite, troisième bouton).

À partir d'ici, changeons le nom de la classe ViewController en SearchResultsViewController. Maintenant, nous devrions être de retour sur la bonne voie. Vérifions que le projet fonctionne toujours et continuons.

Déplaçons maintenant le code de l'API vers sa propre classe. Faites un clic droit dans le volet de navigation de Xcode et sélectionnez « Nouveau fichier », qui sera un fichier Swift ordinaire, sous l'option de navigation iOS → Source.

Celui-ci gère tout le travail de l'API, donc je vais l'appeler APIController.

Maintenant, nous allons couper le code de notre fonction searchItunesFor() du contrôleur de recherche et le coller à l'intérieur d'une nouvelle classe APIController.

Votre fichier complet APIController.swift devrait ressembler à ceci :

1. import Foundation

3. class APIController {

5. func searchItunesFor(searchTerm: String) {

7. // L'API iTunes demande des termes multiples, séparés par des + , alors remplacez les espaces par des +

8. let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)

10. // Maintenant échappez tout ce qui n'est pas URL-friendly

11. if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {

12. let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"

13. let url: NSURL = NSURL(string: urlPath)

14. let session = NSURLSession.sharedSession()

15. let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in

16. println("Tâche terminée")

17. if(error) {

18. // Si une erreur survient lors de la requête web, l'afficher en console

19. println(error.localizedDescription)

20. }

21. var err: NSError?

23. var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary

24. if(err != nil) {

25. // Si une erreur survient pendant l'analyse du JSON, l'afficher en console

26. println("Erreur JSON \(err!.localizedDescription)")

27. }

28. let results: NSArray = jsonResult["results"] as? NSArray

29. dispatch_async(dispatch_get_main_queue(), {

30. self.tableData = results

31. self.appsTableView!.reloadData()

32. })

33. })

34. task.resume()

35. }

36. }

37. }

Si vous essayez de compiler tout de suite, vous verrez trois erreurs :

1. searchItunesFor() est maintenant indéfinie dans notre SearchResultsViewController ;

2. self.tableData est maintenant indéfinie dans APIController ;

3. self.appsTableView est maintenant indéfinie dans APIController.

Pour y remédier, nous devons laisser ces objets se connaître les uns les autres. Faisons donc d'APIController un objet enfant de notre SearchResultsViewController. C'est assez facile à faire, il suffit d'ajouter la ligne suivante en dessous de votre définition de la classe SearchResultsViewController :

1. let api = APIController()

Maintenant, modifiez la ligne qui contient l'appel à searchItunesFor() ainsi :

1. api.searchItunesFor("Angry Birds")

La seule différence ici est que nous appelons la méthode de l'intérieur d'une instance APIController plutôt que de faire tout cela à partir d'une méthode dans le View Controller.

Cela règle la première série d'erreurs, mais maintenant nous devons aussi obtenir les résultats d'APIController dans SearchResultsViewController. Nous aimerions que notre contrôleur de l'API puisse répondre aux appels d'API arbitraires, donc nous devrions définir un protocole auquel les vues peuvent souscrire afin d'obtenir des résultats !

III - Définir un protocole d'API

Il y a maintenant dans la méthode dispatch_async() de notre APIController deux lignes aux erreurs qui référencent les résultats tableData : supprimons-les tout simplement. Nous allons utiliser quelque chose d'un peu plus propre.

1. dispatch_async(dispatch_get_main_queue(), {  // EFFACEZ-MOI!

2. self.tableData = results                 // EFFACEZ-MOI!

3. self.appsTableView!.reloadData()         // EFFACEZ-MOI!

4. })                                           // EFFACEZ-MOI!

Au-dessus de la définition de classe dans notre APIController, nous allons ajouter un protocole qui déclare la fonction didReceiveAPIResults() comme une implémentation requise. Notez que le protocole va à l'extérieur de la définition de la classe, alors assurez-vous qu'il est en dehors des accolades de APIController{}.

1. protocol APIControllerProtocol {

2. func didReceiveAPIResults(results: NSArray)

3. }

Ceci ne fait rien tout seul, mais maintenant nous pouvons ajouter le protocole à notre SearchResultsViewController. Le non-respect du protocole va désormais provoquer une erreur, nous n'allons donc pas faire la bêtise de ne pas implémenter didReceiveAPIResults !

IV - Adhérer au protocole

Maintenant, modifiez votre SearchResultsViewController pour adhérer au protocole :

1. class SearchResultsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, APIControllerProtocol {

La compilation devrait maintenant donner une erreur disant que SearchResultsViewController n'est pas conforme à APIControllerProtocol, c'est parfait ! C'est exactement ce que fait un protocole. Il provoque le compilateur à cracher des erreurs si vous aviez l'intention de mettre en œuvre un protocole, mais ne le faites pas jusqu'au bout. Dans ce cas-ci, il râle parce qu'il nous manque la méthode de rappel didReceiveAPIResults(), ajoutons-la.

Nous avons juste besoin d'ajouter la fonction à l'intérieur de notre classe SearchResultsViewController. Elle ressemblera assez bien au contenu de notre fermeture dataTaskWithURL d'avant.

1. func didReceiveAPIResults(results: NSArray) {

2. dispatch_async(dispatch_get_main_queue(), {

3. self.tableData = results

4. self.appsTableView!.reloadData()

5. })

6. }

La seule chose qui reste à faire est de modifier notre APIController pour y inclure un objet délégué, et d'appeler cette méthode lorsque la connexion a fini de charger des données d'API.

V - Utiliser le protocole

De retour dans notre fichier APIController.swift, nous allons ajouter le délégué et un constructeur vide, juste en dessous de la définition de classe.

1. var delegate: APIControllerProtocol?

Le point d'interrogation à la fin indique que le délégué est une valeur optionnelle. Sans le point d'interrogation, nous allons obtenir une erreur de compilation pour ne pas avoir utilisé une valeur initiale de cette variable, mais avec lui, nous sommes en ordre. L'objet délégué peut ici appartenir à n'importe quelle classe, tant qu'il adhère à l'APIControllerProtocol en implémentant la méthode didReceiveAPIResults(), comme nous l'avons fait dans notre SearchResultsViewController.

Après avoir ajouté le délégué, retournons à notre SearchResultsViewController, et dans la méthode viewDidLoad, définissons notre contrôleur d'API comme délégué pour lui-même, ainsi il recevra des appels de délégué.

1. api.delegate = self

Enfin, dans la fermeture dataTaskWithURL à l'intérieur de notre méthode searchItunesFor(), nous allons remplacer le code du rafraîchissement de la tableView par notre nouvelle méthode de protocole. Celle-ci passera la responsabilité de la mise à jour de l'interface utilisateur au délégué, dans notre cas SearchResultsViewController.

Ceci remplacera les lignes de code qui rafraîchissaient la tableview, supprimées tout à l'heure.

1. if let results: NSArray = jsonResult["results"] as? NSArray {

2. self.delegate?.didReceiveAPIResults(results)

3. }

Voici le code complet du fichier APIController.swift à ce stade :

1. import Foundation

3. protocol APIControllerProtocol {

4. func didReceiveAPIResults(results: NSArray)

5. }

7. class APIController {

8. var delegate: APIControllerProtocol?

10. func searchItunesFor(searchTerm: String) {

11. // L'API iTunes demande des termes multiples, séparés par des + , alors remplacez les espaces par des +

12. let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)

14. // Maintenant échappez tout ce qui n'est pas URL-friendly

15. if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {

16. let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"

17. let url: NSURL = NSURL(string: urlPath)

18. let session = NSURLSession.sharedSession()

19. let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in

20. println("Tâche terminée")

21. if(error!= nil) {

22. // Si une erreur survient lors de la requête web, l'afficher en console

23. println(error.localizedDescription)

24. }

25. var err: NSError?

26. if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary

27. if(err != nil) {

28. // Si une erreur survient pendant l'analyse du JSON, l'afficher en console

29. println("Erreur JSON \(err!.localizedDescription)")

30. }

31. if let results: NSArray = jsonResult["results"] as? NSArray {

32. self.delegate?.didReceiveAPIResults(results)

33. }

34. }

35. })

36. // task est juste un objet avec toutes ces propriétés définies

37. // Afin d'exécuter réellement la requête Web, nous devons appeler resume()

38. task.resume()

39. }

40. }

41. }

Problème commun survenu à ce stade

*** Terminating app due to uncaught exception ‘NSUnknownKeyException', reason: ‘[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key appsTableView'.

Si vous obtenez cette erreur, elle pourrait être causée par quelques bogues dans la version de Xcode concernant le changement de nom des sous-classes de View Controller. La meilleure façon de résoudre ce problème est d'ouvrir le storyboard et sélectionner l'objet « Search Results View Controller ». Puis, dans l'inspecteur d'identité, changer son nom en UIViewController, compiler l'application, puis le nommer à nouveau en SearchResultsViewController. Cela réinitialise certaines choses et résout le problème.

VI - OUF !

Bon, je sais que cela peut vous sembler beaucoup de baratin, et maintenant notre application fait exactement la même chose qu'avant, mais nous avons enfin quelque chose de beaucoup plus souple. Nous pouvons désormais utiliser APIController pour tout appel à l'API de recherche d'iTunes et avons un délégué personnalisé pour obtenir la réponse. Je pense que nous n'avons plus le temps, nous allons donc passer à l'interaction dans la partie 4.

Je sentais que cette étape était plus importante que la résolution des problèmes de performance, mais je vous promets que nous allons accélérer les choses avant de terminer. Il y aura juste certaines choses que nous devrons faire en premier. Accrochez-vous !

VII - Code source

Le code source complet de cette section est disponible ici.

VIII - Vous avez une question ou un problème ?

Rejoignez-nous sur nos forums.

IX - Remerciements Developpez

Nous remercions Jameson Quave de nous avoir aimablement autorisés à publier son article, dont le texte original peut être trouvé sur jamesonquave.com. Nous remercions aussi Mishulyna pour sa traduction, LeBzul pour sa relecture technique ainsi que jacques_jean et ClaudeLELOUP pour leur relecture orthographique.

Aller à la partie 4 maintenant ->

Retrouvez toute la série « Développer des Apps iOS 8 avec Swift »