Nous avons maintenant une structure Question
et nous allons donc pouvoir créer notre classe Game
. Contrairement à la structure Question
, cette classe va contenir beaucoup de logique. C'est la raison pour laquelle, nous allons choisir une classe plutôt qu'une structure.
Exercice : Création d’une classe Game
Et on commence très fort ce chapitre avec déjà un exercice ! En effet, vous savez très bien créer des classes, donc je vous propose de la créer sans moi dans cet exercice. Faites-le vraiment sinon vous risquez d'être perdus lorsqu'on devra utiliser notre classe Game
.
Une fois l'exercice terminé, vous pouvez télécharger le fichier Game.swift et le glisser dans votre modèle (c'est la même opération que ce qu'on a fait avec la police Balham).
QuestionManager
Notre quizz va charger ses questions depuis internet. Plus précisément, nous allons charger nos questions depuis la base de données de questions gratuites : Open Triva Database. Les requêtes réseau ne sont pas au programme de ce chapitre donc je l'ai fait pour vous. Vous pouvez donc télécharger le fichier : QuestionManager.swift. Vous pouvez ensuite glisser ce fichier dans votre modèle.
Ce fichier est un peu compliqué et nous n'allons pas rentrer dans le détail de ce qu'il fait. Sachez seulement qu'il récupère 10 questions dans la base de données, les formatte et les renvoie. Il le fait via la seule méthode publique de cette classe qui a pour signature :
get(completionHandler: @escaping ([Question]) -> ())
Et c'est cette méthode que nous allons utiliser pour finaliser la méthode refresh
de notre classe Game
.
Doucement mon garçon. Tu veux vraiment qu'on utilise cette méthode ?
Oui, pourquoi ?
Tu as vu la tête du machin ?!!
Bon OK, ça peut paraître un peu impressionnant, mais rassurez-vous. À la fin de chapitre, cela n'aura plus de secret pour vous.
D'ailleurs vous en comprenez déjà une bonne partie. Nous avons une fonction qui s'appelle get
et qui admet un paramètre completionHandler
.
Il nous reste à voir ensemble ce que veut dire : @escaping ([Question]) -> ()
. Mettons le @escaping
de côté pour le moment et intéressons-nous à ([Question]) -> ()
.
Le type fonction
Ceci : ([Question]) -> ()
est un type un peu particulier : le type fonction. Oui, les fonctions sont un type. De toute façon, tout est type en Swift !
Le type fonction a une syntaxe un peu particulière. On écrit d'abord entre parenthèses, les types des paramètres puis une flèche puis le type de retour. Prenons quelques exemples de fonctions et tâchons de trouver leur type.
func double(a: Int) -> Int { (...) } // (Int) -> Int
func multiplie(a: Int, b: Int) -> Int { (...) } // (Int, Int) -> Int
func envoyer(message: String) -> Bool { (...) } // (String) -> Bool
func annuler() { (...) } // () -> ()
func annuler() { (...) } // () -> Void
func saluer(personne: String) // (String) -> ()
Ce n'est donc pas plus compliqué que ça ! Donc si on fait le travail inverse : le type ([Question]) -> ()
décrit une fonction qui prend en paramètre un tableau de questions et qui ne renvoie pas de valeur.
Le type question est donc un type et par conséquent il peut être utilisé n'importe où : comme type d'une variable, comme paramètre ou valeur de retour d'une fonction, comme type d'un tableau etc. Je vous propose de jouer un peu avec dans le Playground. Copiez les 4 fonctions suivantes :
func ajouteDeux(x: Int) -> Int {
return x + 2
}
func multiplieParTrois(x: Int) -> Int {
return x * 3
}
func soustraitQuatre(x: Int) -> Int {
return x - 4
}
func multiplieParDeux(x: Int) -> Int {
return x * 2
}
Ces quatre fonctions sont du même type : (Int) -> Int
. Donc on peut créer un tableau qui les regroupe toutes :
var mesFonctions = [ajouteDeux(x:), multiplieParTrois(x:), soustraitQuatre(x:), multiplieParDeux(x:)]
Ce tableau est donc du type [(Int) -> Int]
. Maintenant je peux, par exemple, faire une boucle for pour parcourir mon tableau :
var a = 2
for maFonction in mesFonctions {
a = maFonction(a)
}
À chaque tour du tableau, c'est une fonction différente qui est utilisée et a
vaut 16 à la fin.
Une autre façon de renvoyer une valeur de retour
Il y a une question que vous ne m'avez pas encore posée !
Pourquoi la fonction get
de QuestionManager
a une fonction en paramètre ?
Exactement ! Vous êtes impressionnant ;) !
Le rôle de la fonction get
est juste de renvoyer un tableau de questions téléchargées sur internet.
Pourquoi elle ne ressemble pas juste à : func get() -> [Question]
?
Excellente question ! En fait, cette fonction est un peu particulière, car on ne sait pas quand la valeur de retour va arriver. Cela va dépendre de la qualité du réseau et du temps de réponse du serveur etc. Autrement dit, dès qu'on va chercher des ressources sur internet, on est obligé d'attendre la réponse. Or une fonction avec valeur de retour s'exécute instantanément et donc les questions n'ont pas le temps d'être chargées.
Du coup à la place, on donne à l'utilisateur de la méthode get
la possibilité d'exécuter une fonction lorsque le chargement des questions est terminé. On passe alors en paramètre de cette fonction à exécuter les questions que nous venons de charger.
Donc nous allons créer une méthode dans notre classe Game
que l'on va appeler receiveQuestion
dont le rôle va être de gérer les questions reçues. Cette fonction va être du type ([Question]) -> ()
:
private func receiveQuestions(_ questions: [Question]) {
self.questions = questions
state = .ongoing
}
Lorsqu'on reçoit les questions, on fait donc deux choses :
Affecter les questions à la propriété
questions
deGame
.Passer la propriété
state
deGame
à la valeurongoing
. Les questions sont chargées, la partie peut reprendre.
Maintenant nous allons pouvoir passer cette nouvelle fonction en paramètre de la fonction get
. Et nous allons faire cela dans notre méthode refresh
.
func refresh() {
score = 0
currentIndex = 0
state = .over
QuestionManager.shared.get(completionHandler: receiveQuestions)
}
Ici, on appelle donc la méthode get
du QuestionManager
et on lui passe en paramètre la méthode receiveQuestions
. Cela signifie que lorsque les questions sont chargées, la méthode receiveQuestions
est appelée.
let game = Game()
game.refresh()
Ensuite, en ajoutant un print(questions)
dans la méthode receiveQuestions
, vous pouvez vérifier que tout fonctionne bien en lançant l'application.
Vous voyez ? Nous avons réussi à utiliser cette méthode get
. Ce n'était pas si dur finalement ! Notre méthode refresh
est maintenant complète et permet de remettre à zéro les paramètres de la partie, de charger les questions et de relancer la partie.
Exercice
1/ Trouvez les types des fonctions suivantes
func ajouterDeux(a: Int) -> Int { (...) }
func additioner(a: Int, b: Int) -> Int { (...) }
func envoyerMail(message: String, destinataire: String) -> Bool { (...) }
func cocherLaCase(aCoché: Bool) { (...) }
func verrouiller() { (...) }
func composerNumero(_ numero: Int) { (...) }
Vous pouvez trouver la correction ici.
2/ Utiliser les types fonctions
Dans cet exercice, on cherche à calculer la somme d'un tableau d'entier selon les règles suivantes :
si le nombre est pair, on le divise par deux avant de l'aditionner aux autres
si le nombre est impair, on ajoute un puis on le divise par deux avant de l'additioner aux autres
Le code suivant vous est déjà fourni :
func diviserNombrePairParDeux(x: Int) -> Int {
return x / 2
}
func diviserNombreImpairParDeux(x: Int) -> Int {
return (x + 1) / 2
}
func obtenirDivision(x: Int) -> (Int) -> (Int) {
// complétez cette fonction
}
let tableau = [2, 12, 3, 14, 76, 19, 7, 22]
var somme = 0
for nombre in tableau {
// complétez cette boucle
}
Vous devez compléter la fonction obtenirDivision
. Cette fonction renvoie une des deux fonctions au dessus en fonction de la parité de son paramètre x
. Vous noterez que le type de retour de cette fonction corresponds bien au type des deux fonctions du dessus. Ensuite, vous devez utiliser la fonction obtenirDivision
pour compléter la boucle.
Une fois l'exercice terminé, vous pouvez aller lire la correction ici.
En résumé
Les fonctions sont des types, on appelle cela le type fonction.
Le type fonction a pour syntaxe :
(TypeParam1, TypeParam1, TypeParam1) -> TypeRetour
.On peut utiliser les types fonction pour passer des fonctions en paramètre d'autres fonctions notamment.