From c0c0ff8e0f3ae6ebf854656b14877cdc64a2ea3f Mon Sep 17 00:00:00 2001 From: HHK Date: Sun, 15 Apr 2018 18:20:04 -0700 Subject: [PATCH 1/5] relecture partie 1 --- "8. Appels r\303\251seaux/Content/part1.md" | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git "a/8. Appels r\303\251seaux/Content/part1.md" "b/8. Appels r\303\251seaux/Content/part1.md" index 24300a3..cab12cc 100755 --- "a/8. Appels r\303\251seaux/Content/part1.md" +++ "b/8. Appels r\303\251seaux/Content/part1.md" @@ -125,10 +125,10 @@ jsonp= — callback function name, used for jsonp format only (usage exa Voyons ceux qui nous intéressent : - `method` : il a l'air obligatoire et c'est celui qui nous permet de récupération la citation avec la valeur `getQuote`. -- `format` : le type de format que l'on souhaite pour la réponse. La documentation précise les différents formats disponibles. Je vous propose qu'on choisisse `json` car c'est un format de donnée très populaire dans le développement mobile pour sa petite taille. +- `format` : le type de format que l'on souhaite pour la réponse. La documentation précise les différents formats disponibles. Je vous propose qu'on choisisse `json` car c'est un format de donnée très populaire dans le développement mobile. - `key` : ce paramètre n'est pas très clair et nous allons du coup essayer sans. - `lang` : il y a deux langages disponibles, on va favoriser l'anglais (sauf si vous parlez russe !) et choisir du coup la valeur `en`. -- `jsonp` : nous n'avons pas besoin de ça non plus. +- `jsonp` : nous n'avons pas besoin de ça non plus puisqu'on va utiliser du json. Du coup, nous allons indiquer 3 paramètres : @@ -191,15 +191,17 @@ Il existe plusieurs types de tâches. Pour chaque type de tâche, Apple a créé > **:information_source:** Les deux dernières tâches vous permettent de suivre et donc d'indiquer à l'utilisateur la progression du chargement. -> **:warning:** En pratique, **vous n'utiliserez donc jamais URLSessionTask,** mais une de ses trois sous-classes. Dans ce genre de cas, on dit qu'`URLSessionTask` est une classe *abstraite*. +> **:warning:** En pratique, **vous n'utiliserez donc jamais URLSessionTask,** mais une de ses trois sous-classes. Dans ce genre de cas, on dit qu'`URLSessionTask` est une classe *abstraite*. Elle définit un comportement - gérer une requête et sa réponse, et les sous-classes implémentent de façon concrète ce comportement: télécharger / uploader un fichier, envoyer / recevoir des données. + #### Format de la réponse -Une fois la requête envoyée, la réponse est formatée et disponible dans la classe `URLResponse` ou dans sa sous-classe `HTTPURLResponse` (spécifique aux requêtes HTTP). Vous pouvez notamment y vérifier le `status code` de la requête pour vérifier que la requête a fonctionné. +Une fois la requête envoyée, la réponse est formatée et disponible dans la classe `URLResponse` ou dans sa sous-classe `HTTPURLResponse` (spécifique aux requêtes HTTP). Vous pouvez notamment y vérifier le `status code` de la réponse pour vérifier que la requête a fonctionné. Cette réponse est accompagnée d'une éventuelle erreur (`Error`) et d'éventuelles données (`Data`). #### En résumé - `URLSession` est initialisée avec `URLSessionConfiguration` et permet de lancer des requêtes avec les 3 sous-classes d'`URLSessionTask` : `URLSessionDataTask`, `URLSessionUploadTask` et `URLSessionDownloadTask`. +- `URLRequest` permet de spécifier la requête: quelle URL, quelle méthode, quels paramètres: Quel endpoint de l’API j’utilise et comment. - La réponse est le plus souvent disponible en trois objets : `URLResponse` (ou `HTTPURLResponse`), `Data` et `Error`. Dans le prochain chapitre, nous allons utiliser la suite de classes `URLSession` pour lancer notre première requête avec Swift ! @@ -231,6 +233,8 @@ private static let quoteUrl = URL(string: "https://api.forismatic.com/api/1.0/") > **:information_source:** Je vous suggère de bien indiquer en haut de vos classes vos URL pour qu'elles soient bien visibles et donc modifiables facilement si besoin. +> **:information_source:** Sur de plus gros projets où vous utilisez votre propre API, je vous conseille de séparer la racine de l'url de la partie variable. Si vous avez de nombreux appels réseaux et que vous changez de version d'API, cela vous évitera de changer *toutes* vos URL. + Maintenant, nous allons créer une fonction statique que nous allons appeler simplement `getQuote` et à l'intérieur de laquelle nous allons créer notre requête : @@ -245,6 +249,15 @@ J'initialise une instance de `URLRequest` en lui passant notre url en paramètre Par ailleurs, nous avons besoin de passer des paramètres dans cette requête. Nous allons les rajouter avec la propriété `body` de `URLRequest` : +**Sur ce passage je suis moyen convaincu par l'utilisation d'une String avec la paramètres. C'est un peu crade, et sur des requêtes plus complexes, ça rend la lecture vraiment difficile. Tu peux sans doute utiliser un Array [String: Any], et le serializer en data. C'est un peu plus compliqué, mais tellement plus propre. +Il y'a un conflit du coup avec la suite quand tu introduis la sérialization de la réponse (l'introduction du try, notamment)** + +``` swift +let parameters: [String: Any] = ["method": "getQuote", "lang": "en", "format": "json"] +let body = try? JSONSerialization.data(withJSONObject: parameters) +request.httpBody = body +``` + ```swift let body = "method=getQuote&lang=en&format=json" request.httpBody = body.data(using: .utf8) @@ -647,6 +660,8 @@ Vous pouvez télécharger la correction [ici](https://s3-eu-west-1.amazonaws.com #### Nom des notifications +**Comme je t'ai dit, je suis pas très fan de cette approche, utiliser des callbacks me semble une option bien plus solide à enseigner. ** + Souvenez-vous, en MVC, **le modèle discute avec le contrôleur via les notifications**. ![](Images/P1/P1C6_1.png) From 472ca47ccd55835479a84452c5203d44cee11734 Mon Sep 17 00:00:00 2001 From: HHK Date: Sun, 15 Apr 2018 18:22:55 -0700 Subject: [PATCH 2/5] give details around callbacks vs notifications --- "8. Appels r\303\251seaux/Content/part1.md" | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git "a/8. Appels r\303\251seaux/Content/part1.md" "b/8. Appels r\303\251seaux/Content/part1.md" index cab12cc..b2211b6 100755 --- "a/8. Appels r\303\251seaux/Content/part1.md" +++ "b/8. Appels r\303\251seaux/Content/part1.md" @@ -660,7 +660,10 @@ Vous pouvez télécharger la correction [ici](https://s3-eu-west-1.amazonaws.com #### Nom des notifications -**Comme je t'ai dit, je suis pas très fan de cette approche, utiliser des callbacks me semble une option bien plus solide à enseigner. ** +**Comme je t'ai dit, je suis pas très fan de cette approche, utiliser des callbacks me semble une option bien plus solide à enseigner. +- les callbacks donnent plus d'informations au développeur qui va consommer tes fonctions: je vais recevoir ici un objet de type Quote, et une erreur éventuellement +- le callback te force à faire qqch du résultat de ta requête +- les notifications sont utiles lorsque tu dois notifier plusieurs objets, ou lorsque l'objet qui réceptionne l'information est "éloigné" (n'a pas de connaissances) de l'objet qui requête l'information, ce qui n'est pas le cas ici.** Souvenez-vous, en MVC, **le modèle discute avec le contrôleur via les notifications**. From fd9edd3f96a90c77e33dae03feb9e54d674427b3 Mon Sep 17 00:00:00 2001 From: HHK Date: Sun, 15 Apr 2018 18:51:38 -0700 Subject: [PATCH 3/5] relecture partie 2 --- "8. Appels r\303\251seaux/Content/part2.md" | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git "a/8. Appels r\303\251seaux/Content/part2.md" "b/8. Appels r\303\251seaux/Content/part2.md" index 1e15139..4430552 100755 --- "a/8. Appels r\303\251seaux/Content/part2.md" +++ "b/8. Appels r\303\251seaux/Content/part2.md" @@ -102,7 +102,7 @@ Qu'à cela ne tienne, on va y retourner ! #### Résolution du problème -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. Ou pour revenir dans la Main Queue, on écrit : +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 : ```swift DispatchQueue.main.async { @@ -202,6 +202,8 @@ On appelle la fonction `getQuote` sur une instance de `QuoteService` et non sur #### Annuler une tâche +**Gros caveat sur cette partie: si je commence à avoir plusieurs calls en parallèle et que j'implémente ton système, je vais annuler mes calls précédent. Tu donnes un side effect à ta fonction getQuote qui peut rapidement être indésirable. La partie singleton est très bien, mais sur l'aspect mono-task, je veux bien voir d'autres sources qui font ça si tu en as.** + Avec ce petit travail préalable, **nous allons pouvoir travailler sur une instance fixe de `task`** et donc on va pouvoir annuler la tâche si une autre tâche est lancée. On a vu qu'on pouvait lancer un appel avec la méthode `resume` de `URLSessionTask`. Pour l'annuler, on va utiliser la méthode `cancel`. From 8f6743633bb516a0bc612ed1b6480e92a918f160 Mon Sep 17 00:00:00 2001 From: HHK Date: Mon, 16 Apr 2018 22:47:26 -0700 Subject: [PATCH 4/5] relecture partie 3 --- "8. Appels r\303\251seaux/Content/part3.md" | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git "a/8. Appels r\303\251seaux/Content/part3.md" "b/8. Appels r\303\251seaux/Content/part3.md" index b64076d..0e826f4 100755 --- "a/8. Appels r\303\251seaux/Content/part3.md" +++ "b/8. Appels r\303\251seaux/Content/part3.md" @@ -124,6 +124,8 @@ Puis nommez le `Quote.json` et sauvegardez-le du côté des tests. ![](Images/P3/P3C2_4.png) +**Peut être expliquer le pourquoi du comment d'une target, et mettre un screenshot du File Inspector (right panel) pour montrer qu'on peut le changer plus tard. Parce que comme je suis bête j'oublie tout le temps de changer la target quand je créé mon fichier** + Dedans, vous pouvez coller la réponse récupérée dans Postman. ![](Images/P3/P3C2_5.png) @@ -297,6 +299,8 @@ class FakeResponseData { Dans le prochain chapitre, nous allons préparer notre classe `QuoteService` à être testée et vous allez découvrir un concept important en architecture logicielle : l'injection de dépendance. +**Est-ce que ça vaut le coup de mentionner le swizzling ? Injection au compile time vs au runtime ?** + Et rassurez-vous, ça n'est pas aussi impressionnant que ça en a l'air ! ### Préparez votre classe à être testée @@ -469,6 +473,8 @@ Et voilà, vous avez fait vos deux premières injections de dépendance ! C'éta #### Juste pour faire propre Je n'aime pas trop avoir des propriétés publiques qui ne devraient pas l'être. Pourtant les tests ont besoin d'accéder à ces propriétés. Mais je vous propose du coup de créer plutôt un initialiseur pour ces deux propriétés et de les laisser privées. +**Le problème avec cette injection de session c'est que tu te retrouves avec une session par call. Si je veux tester les 25 calls différents de mon app, ça devient le bazar. Je ne sais pas si c'est nécessaire de le relever, mais ça peut être intéressant de refactorer ton fake de URLSession pour l'instancier avec un dictionnaire qui map une URL à une closure, ou de lui ajouter des méthodes pour rajouter des calls.** + ```swift private var quoteSession = URLSession(configuration: .default) private var imageSession = URLSession(configuration: .default) @@ -682,6 +688,8 @@ Et voilà nos doubles sont tout prêt et nous allons pouvoir tester ! #### Par où passe le code ? +**Effectivement, c'est clair, mais la première fois, c'est pas évident quand même. Un petit schéma qui compare la structure entre le test et l'original serait chouette non ?** + J'ai conscience que tout ceci n'est pas évident à digérer alors je vous propose qu'on prenne du recul pour comprendre par où passe le code. Dans nos tests, nous allons d'abord créer une instance de `QuoteService` avec l'initialiseur suivant : @@ -1054,9 +1062,9 @@ J'ai conscience que cette partie était relativement difficile. Mais je crois fo Et pour ne pas finir comme la grande majorité des développeurs qui procrastinent l'intégration des tests dans leurs habitudes, il vous faut apprendre avec les tests. Car **les tests et le code en production sont aussi importants l'un que l'autre.** > **:warning:** Je vous recommande vivement de faire une pause : prenez un cookie, faites du sport, nettoyez votre salle de bain ou allez vous coucher ! -> +> > Et en revenant, **relisez l'intégralité de cette partie**. -> +> > Cet exercice ne vous prendra pas beaucoup de temps. Mais maintenant que vous avez la vue d'ensemble, il permettra à votre cerveau de finaliser les connexions qui ne se sont pas encore faites et vous permettra d'assimiler en profondeur le contenu riche de cette partie ! Je vous donne ensuite rendez-vous dans la prochaine partie où nous allons parler de la gestion d'erreur en `Swift` et lever le voile sur ces mystérieux try que nous avons croisé ensemble ! From f472cdf8e95adaf6849a00863ef0180c6a84ad0b Mon Sep 17 00:00:00 2001 From: HHK Date: Mon, 16 Apr 2018 23:08:17 -0700 Subject: [PATCH 5/5] relecture partie 4 --- "8. Appels r\303\251seaux/Content/part4.md" | 43 +++++++++++---------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git "a/8. Appels r\303\251seaux/Content/part4.md" "b/8. Appels r\303\251seaux/Content/part4.md" index 5d6ba2f..73fc0c7 100755 --- "a/8. Appels r\303\251seaux/Content/part4.md" +++ "b/8. Appels r\303\251seaux/Content/part4.md" @@ -1,8 +1,8 @@ ## Gérez les erreurs proprement - + ### Lancez des erreurs -Pendant ce cours, nous avons croisé différentes petites choses qui ne vous sont pas forcément familières. On a notamment parlé du protocole `Error` avec notre classe `QuoteError` et on a croisé le mot-clé `try`. On n'est pas rentré dans le détail, car je ne voulais pas vous perturber avec trop d'informations à la fois. +Pendant ce cours, nous avons croisé différentes petites choses qui ne vous sont pas forcément familières. On a notamment parlé du protocole `Error` avec notre classe `QuoteError` et on a croisé le mot-clé `try`. On n'est pas rentré dans le détail, car je ne voulais pas vous perturber avec trop d'informations à la fois. Mais maintenant que les choses sont derrière nous, nous allons voir tout ça sérieusement ! @@ -13,7 +13,7 @@ Tout cela est du domaine de la gestion d'erreur en Swift. Concernant ce sujet, n - le protocole `error` et le mot-clé `throw` que nous allons voir **dans ce chapitre**. - les mot-clés `try`, `do`, `catch` dont nous allons parler **dans le prochain chapitre**. -Pour découvrir tout ça, je vous propose de reprendre le Playground. Oui je sais, ça vous manquait ;) ! +Pour découvrir tout ça, je vous propose de reprendre le Playground. Oui je sais, ça vous manquait ;) ! **Je vous invite à télécharger [ici](https://s3-eu-west-1.amazonaws.com/static.oc-static.com/prod/courses/files/Parcours+DA+iOS/Cours+8+-+Appels+réseaux/OrderError.playground.zip) un Playground que j'ai préparé pour vous**. @@ -36,13 +36,16 @@ C'est parti ! > **:information_source:** Nous n'avons pas encore parlé en détail des protocoles. On les a seulement survolés. Un protocole permet, comme une classe ou une structure de **définir un type**. Mais à la différence de ces derniers, il n'y a **pas d'implémentation**. C'est simplement une liste de fonctions. +**C'est dommage de souligner le fait qu'il n'y a pas d'implémentation dans le protocol, alors que Swift permet justement de donner une implémentation aux fonctions de ton protocol via les extensions de protocol. +C'est vrai en Java par example avec les interfaces, mais pas en swift** + #### Créer les erreurs La façon la plus classique d'utiliser ce protocole, c'est de **définir une énumération** en lui attribuant le protocole `Error`. Nous allons donc définir une énumération `OrderError` dans notre classe `Order` comme ceci : ```swift enum OrderError: Error { } -``` +``` Dans cette énumération, nous allons lister les différents cas d'erreur possibles dans notre code. Il va y en avoir 4 : @@ -74,7 +77,7 @@ guard status == .pending else { Plusieurs choses à noter ici : -- `throw` vous fait quitter le contexte du code. Comme `return`, **lorsque `throw` est appelé on quitte la fonction**. +- `throw` vous fait quitter le contexte du code. Comme `return`, **lorsque `throw` est appelé on quitte la fonction**. > **:information_source:** Cela vous permet de voir une nouvelle façon de remplir la partie `else` d'un `guard`. @@ -87,7 +90,7 @@ func pay(with paymentMethod: PaymentMethod) throws -> Double { // On ajoute thro guard status == .pending else { throw OrderError.orderAlreadyPayed } - + // (...) } ``` @@ -134,22 +137,22 @@ func pay(with paymentMethod: PaymentMethod) throws -> Double { guard status == .pending else { throw OrderError.orderAlreadyPayed } - + guard items.count > 0 else { throw OrderError.orderIsEmpty } - + var totalPrice = 0.0 for item in items { totalPrice += item.price } - + guard paymentMethod.isValid else { throw OrderError.invalidPaymentMethod } - + guard paymentMethod.maxAmount >= totalPrice else { throw OrderError.insufficientFundings } - + status = .payed return totalPrice } @@ -170,7 +173,7 @@ Vous savez comment créer des erreurs, voyons maintenant comment les gérer ! #### Le mot-clé try Nous allons enfin parler de ce fameux mot-clé `try` que nous avons croisé plusieurs fois dans ce cours. -Copiez le code suivant dans votre Playground : +Copiez le code suivant dans votre Playground : ```swift func payFruits() { @@ -180,7 +183,7 @@ func payFruits() { Item(price: 4, description: "Fraises"), Item(price: 1.20, description: "Pomme") ] - + let cb = PaymentMethod(isValid: true, maxAmount: 100) let price = order.pay(with: cb) print("Votre commande d'un montant de \(price)€ a bien été prise en compte.") @@ -211,7 +214,7 @@ Errors thrown from here are not handled. ``` C'est normal ! Il ne suffit pas d'écrire le mot-clé `try`, il faut aussi gérer l'erreur et il y a deux façons de le faire. - + #### Gérer l'erreur avec do/catch Pour gérer proprement les erreurs, il faut entourer l'appel de la fonction par l'instruction `do/catch` comme ceci : @@ -285,7 +288,7 @@ Et voilà une très belle gestion d'erreur ! Votre utilisateur sait maintenant t > **:question:** Certes, mais du coup ça fait beaucoup de code à rédiger ! -Il ne faut pas être fainéant quand il s'agit de la gestion des erreurs ! Néanmoins je suis d'accord que parfois, on n'a pas besoin de donner autant de détail. +Il ne faut pas être fainéant quand il s'agit de la gestion des erreurs ! Néanmoins je suis d'accord que parfois, on n'a pas besoin de donner autant de détail. Dans ces cas-là, il existe une méthode plus rapide. @@ -345,10 +348,10 @@ Ne réservez l'utilisation de `try!` qu'à de rares exceptions. Félicitations ! Ce cours n'était pas facile ! Vous verrez avec la pratique que la gestion des réseaux n'est pas la partie la plus évidente du travail de développeur iOS. Pourtant, **vous venez de faire un grand pas vers la maitrise de ce sujet !** #### En résumé -Dans ce cours, vous avez vu comment lancer des appels réseau avec la classe `URLSession`. +Dans ce cours, vous avez vu comment lancer des appels réseau avec la classe `URLSession`. + +> **:information_source:** Cela veut dire que vous êtes maintenant autonome pour créer une application avec des fonctionnalités sophistiquées comme un login, la gestion d'API tierces ou la communication avec un back-end. -> **:information_source:** Cela veut dire que vous êtes maintenant autonome pour créer une application avec des fonctionnalités sophistiquées comme un login, la gestion d'API tierces ou la communication avec un back-end. - C'est une étape très importante vers la professionnalisation de vos compétences de développeur iOS ! Malgré le fait que vous découvriez tout juste les appels réseau avec Swift, j'ai voulu vous emmener vers des concepts plus avancés comme le pattern singleton ou le multithreading. Je sais que j'ai été particulièrement exigeant avec vous, mais je pense que c'est pour la bonne cause. Il est bien plus facile de prendre de bonnes habitudes dès le début que d'en changer plus tard ! @@ -364,6 +367,6 @@ En particulier pour ce cours, je vous invite à vous exercer en développant une > **:information_source:** Par ailleurs, je vous recommande ce [blog](https://www.swiftbysundell.com). Il parle de comment tester son code, de comment écrire du code Swift propre et claire et des appels réseau. Certains articles sont une bonne suite à ce cours. -En attendant, je n'ai plus qu'à vous laisser avec le mot de la fin et le mot de la fin, évidemment c'est... +En attendant, je n'ai plus qu'à vous laisser avec le mot de la fin et le mot de la fin, évidemment c'est... -![](Images/P4/P4C3_1.jpg) \ No newline at end of file +![](Images/P4/P4C3_1.jpg)