Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions 8. Appels réseaux/Content/part1.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ jsonp=<string> — 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.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not say that JSON is popular because it's small, it's popular because it's simple. And you have options smaller than JSON, such as MessagePack

- `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 :

Expand Down Expand Up @@ -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.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to introduce the notion of concrete subclass


#### 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 !
Expand Down Expand Up @@ -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.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like declaring a baseURL, and also a version that you can always override if need be.


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 :

Expand All @@ -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.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK je vais voir merci

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Si le JSONSerialization n'est pas une option, j'ai découvert ça en cherchant: https://stackoverflow.com/questions/27723912/swift-get-request-with-parameters?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
(comme quoi on en apprend tous les jours)

URLComponents te permet de créer un peu plus proprement tes requêtes GET, si tu veux revisiter cette partie tu peux utiliser ça

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)
Expand Down Expand Up @@ -647,6 +660,11 @@ 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.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je suis d'accord. D'autant plus que ça fait redite avec des cours précédents. Je vais partir sur les callbacks.

- 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**.

![](Images/P1/P1C6_1.png)
Expand Down
4 changes: 3 additions & 1 deletion 8. Appels réseaux/Content/part2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.**

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je vois ce que tu veux dire. J'imaginais que les autres calls seraient dans d'autres classes donc ça ne pose pas de soucis. Comment tu fais les choses toi ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personnellement j'utilise Alamofire, mais simplement le fait d'avoir un effet de bord quand tu fais ton appel réseau est étrange. Par défaut je ne me soucie pas d'executer mon call plusieurs fois, et lorsque le call est lié à une interface utilisateur, je bloque l'interface quand c'est nécessaire.

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`.
Expand Down
12 changes: 10 additions & 2 deletions 8. Appels réseaux/Content/part3.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J'en parle déjà dans d'autres cours.


Dedans, vous pouvez coller la réponse récupérée dans Postman.

![](Images/P3/P3C2_5.png)
Expand Down Expand Up @@ -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 ?**
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je ne connais pas, je vais regarder.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comme dit, ça reste une notion très objective-c. Après réflexion, c'est probablement assez avancé, et même si le parallèle est intéressant, ça risque d'apporter beaucoup de confusion


Et rassurez-vous, ça n'est pas aussi impressionnant que ça en a l'air !

### Préparez votre classe à être testée
Expand Down Expand Up @@ -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.**
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes tu as raison mais j'en demande déjà beaucoup dans ce chapitre.


```swift
private var quoteSession = URLSession(configuration: .default)
private var imageSession = URLSession(configuration: .default)
Expand Down Expand Up @@ -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 ?**
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je ne comprends pas bien de quel schéma tu parles.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

group 5469
un truc comme ça

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Peut-être avec des petites flèches par ci par là pour indiquer quand on override, quand on fait une injection, mais tu as l'idée


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 :
Expand Down Expand Up @@ -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 !
43 changes: 23 additions & 20 deletions 8. Appels réseaux/Content/part4.md
Original file line number Diff line number Diff line change
@@ -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 !

Expand All @@ -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**.

Expand All @@ -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.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu as raison, je n'ai jamais utiliser cette fonctionnalité de Swift, c'est pour ça. Comment tu définirais un protocole du coup ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Nous n'avons pas encore parlé en détail des protocoles. On les a seulement survolés. Un protocol définit un ensemble de méthodes et de fonctions qui vont pouvoir être adoptés par un type. En swift le protocol est un concept très puissant, et peut être adopté par une class, un struct ou même un enum. Il permet ensuite de manipuler des types sans avoir à se soucier de ce qu'il y'a derrière. Je peux créer un protocol Weapon avec une méthode shoot() et une variable ammunition, et je pourrais manipuler différents objets sans savoir si j'utilise un lance-pierre ou une mitraillette."

Voilà une proposition. J'ai fait des drafts en essayant d'expliquer l'histoire de l'implémentation, mais ça devient long, et ça n'est pas vraiment le sujet. Du coup c'est passé sous silence, à expliquer au moment voulu

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C'est top, je le prends tel quel merci !

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 :

Expand Down Expand Up @@ -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`.

Expand All @@ -87,7 +90,7 @@ func pay(with paymentMethod: PaymentMethod) throws -> Double { // On ajoute thro
guard status == .pending else {
throw OrderError.orderAlreadyPayed
}

// (...)
}
```
Expand Down Expand Up @@ -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
}
Expand All @@ -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() {
Expand All @@ -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.")
Expand Down Expand Up @@ -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 :
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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 !
Expand All @@ -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)
![](Images/P4/P4C3_1.jpg)