Développer des Apps iOS 8 avec Swift

Partie 3 : Les bonnes pratiques

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. 3 commentaires Donner une note à l'article (5).

Article lu   fois.

Les deux auteur et traducteur

Site personnel

Traducteur : Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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.

Image non disponible

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.

Image non disponible

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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
import Foundation

class APIController { 

    func searchItunesFor(searchTerm: String) { 

        // L'API iTunes demande des termes multiples, séparés par des + , alors remplacez les espaces par des + 
        let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil) 

        // Maintenant échappez tout ce qui n'est pas URL-friendly 
        if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) { 
            let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software" 
            let url: NSURL = NSURL(string: urlPath) 
            let session = NSURLSession.sharedSession() 
            let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in 
                println("Tâche terminée") 
                if(error) { 
                    // Si une erreur survient lors de la requête web, l'afficher en console 
                    println(error.localizedDescription) 
                } 
                var err: NSError?
 
                var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary 
                if(err != nil) { 
                    // Si une erreur survient pendant l'analyse du JSON, l'afficher en console 
                    println("Erreur JSON \(err!.localizedDescription)") 
                } 
                let results: NSArray = jsonResult["results"] as? NSArray 
                dispatch_async(dispatch_get_main_queue(), { 
                    self.tableData = results 
                    self.appsTableView!.reloadData() 
                }) 
            }) 
            task.resume()
        } 
    } 
}

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 :

 
Sélectionnez
1.
let api = APIController()

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

 
Sélectionnez
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.

 
Sélectionnez
1.
2.
3.
4.
dispatch_async(dispatch_get_main_queue(), {  // EFFACEZ-MOI!
    self.tableData = results                 // EFFACEZ-MOI!
    self.appsTableView!.reloadData()         // EFFACEZ-MOI!
})                                           // 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{}.

 
Sélectionnez
1.
2.
3.
protocol APIControllerProtocol {
   func didReceiveAPIResults(results: NSArray)
}

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 :

 
Sélectionnez
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.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
func didReceiveAPIResults(results: NSArray) { 
    dispatch_async(dispatch_get_main_queue(), { 
        self.tableData = results 
        self.appsTableView!.reloadData() 
    }) 
}

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.

 
Sélectionnez
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é.

 
Sélectionnez
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.

 
Sélectionnez
1.
2.
3.
if let results: NSArray = jsonResult["results"] as? NSArray {
    self.delegate?.didReceiveAPIResults(results)
}

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

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
import Foundation

protocol APIControllerProtocol {
    func didReceiveAPIResults(results: NSArray)
}

class APIController {
    var delegate: APIControllerProtocol?

    func searchItunesFor(searchTerm: String) {    
        // L'API iTunes demande des termes multiples, séparés par des + , alors remplacez les espaces par des + 
        let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
      
        // Maintenant échappez tout ce qui n'est pas URL-friendly 
        if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) { 
            let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software" 
            let url: NSURL = NSURL(string: urlPath) 
            let session = NSURLSession.sharedSession() 
            let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in 
                println("Tâche terminée") 
                if(error!= nil) { 
                    // Si une erreur survient lors de la requête web, l'afficher en console 
                    println(error.localizedDescription) 
                } 
                var err: NSError? 
                if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary 
                    if(err != nil) { 
                        // Si une erreur survient pendant l'analyse du JSON, l'afficher en console 
                        println("Erreur JSON \(err!.localizedDescription)") 
                    } 
                    if let results: NSArray = jsonResult["results"] as? NSArray { 
                        self.delegate?.didReceiveAPIResults(results)
                    }
                }
            })           
            // task est juste un objet avec toutes ces propriétés définies
            // Afin d'exécuter réellement la requête Web, nous devons appeler resume()
            task.resume()
        }
    }
}

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 4Développer des Apps iOS 8 avec Swift : 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 icihttps://github.com/jquave/Swift-Tutorial/tree/Part3.

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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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.