Dans cette partie, nous allons développer le modèle de notre application. Le modèle c'est la logique de l'application, son cerveau. C'est aussi ici que nous allons faire appel à des ressources en base de données ou sur internet.
Allons-y !
Diagramme de classe
Avant de nous lancer tête baissée dans le code, prenons une minute pour dessiner notre diagramme de classes.
Nous allons faire un jeu de quizz. Chaque partie contient 10 questions auxquels on peut répondre par vrai ou faux. Donc, sans plus de détour, on peut rationnellement imaginer 2 classes : une classe Question
et une classe Game
qui va gérer la partie.
Commençons avec la classe la plus simple : Question
. Vous allez créer un nouveau fichier Question.swift
que vous allez placer dans le modèle. Pour cela, vous pouvez aller dans File > New > File...
Choisissez ensuite l'option Swift File et cliquez sur Next :
Puis donner le nom Question.swift
à votre fichier et sauvegardez le dans le modèle.
Et vous voilà avec le fichier Question.swift
!
À vous de jouer maintenant créer la classe Question
avec pour propriété title
de type String
et isCorrect
de type Bool
.
// Faîtes le sans regarder la correction !
class Question {
var title = ""
var isCorrect = false
}
Vous aviez trouvé ? Bravo !
Nous avons maintenant notre classe Question
. Mais en fait, nous n'allons pas faire de classe Question
, nous allons créer une structure Question
!
Vous avez dit structure ?
En Swift, il existe des modèles de données nommés. Ces modèles ont en commun de nous permettre d'organiser nos données en créant des types. Il y en a 3 :
les classes
les énumérations
les structures
Vous connaissez les deux premiers (enseignés dans ce cours) et dans ce chapitre, nous allons étudier le dernier !
Une structure ressemble à 90% à une classe. Les différences sont donc assez subtiles, mais suffisantes pour qu'on s'y intéresse.
Pour créer une structure, on utilise le mot-clé struct
. Vous pouvez donc remplacer dans votre code le mot-clé class
par le mot-clé struct
et obtenir ceci :
struct Question {
var title = ""
var isCorrect = false
}
Et comme vous pouvez le constater, nous n'avons pas d'erreur ! Je vous l'avais dit, les structures sont très proches des classes.
Dans une structure, comme dans une classe, vous pouvez :
Déclarer des propriétés, qu'elles soient calculées ou non, de classe ou d'instance. Tout ce que vous savez sur les propriétés dans les classes est vrai pour les structures.
Déclarer des méthodes : Idem, tout ce que vous savez sur les méthodes dans les classes est vrai pour les structures.
Déclarer des initialiseurs : La plupart de ce que vous savez déjà est vrai pour les structures, mais il y a des petites subtilités que nous verrons.
Alors quelles sont les différences ? Il y en a 2 majeures :
L'héritage n'existe pas avec les structures. Une structure ne peut pas avoir de structure fille ou mère.
Les structures sont des types par valeur tandis que les classes sont des types par référence.
Type par référence et type par valeur
Ouh là, tu viens de dire deux trucs compliqués en même temps ! Tu t'expliques ?
Bien sûr, ça vaut une petite (grosse... ) explication :
Type par référence : lorsqu'on initialise une instance de classe, on déclare à la mémoire de notre ordinateur : "Trouve-moi un endroit où je vais pouvoir stocker un objet". L'ordinateur s'exécute et renvoie une adresse en mémoire où l'instance va pouvoir stocker des informations. Donc quand on écrit :
var instance = MaClasse()
, la variableinstance
ne contient pas un objet, mais seulement une adresse en mémoire (une référence) à laquelle se trouve l'objet.Type par valeur : lorsqu'on initialise une instance de structure, on déclare à la mémoire de notre ordinateur : "Sauvegarde les valeurs X et Y". Donc quand on écrit
var instance = MaStructure()
, l'instance contient directement toutes les valeurs stockées dans l'objet.
Pour bien comprendre tout cela, je vous propose de voir ensemble deux conséquences concrètes de cette différence. Ouvrez un nouveau Playground et copiez le code suivant :
class RectangleClass {
var hauteur = 0
var largeur = 0
init(largeur: Int, hauteur: Int) {
self.largeur = largeur
self.hauteur = hauteur
}
}
struct RectangleStruct {
var hauteur = 0
var largeur = 0
init(largeur: Int, hauteur: Int) {
self.largeur = largeur
self.hauteur = hauteur
}
}
Dans ce code, vous avez une classe RectangleClass
et une structure RectangleStruct
qui ont rigoureusement le même contenu.
Assigner une instance à une autre
Créons une instance de RectangleStruct
:
var s1 = RectangleStruct(largeur: 10, hauteur: 5)
Maintenant, je crée une deuxième variable s2
, je lui affecte la valeur s1
et je modifie sa propriété hauteur :
var s2 = s1
s2.hauteur = 8
Si j'affiche les hauteurs de s1 et s2, j'obtiens bien ceci :
s1.hauteur // 5
s2.hauteur // 8
Tout paraît normal. Les choses deviennent intéressantes si je fais la même chose avec la classe :
var c1 = RectangleClass(largeur: 10, hauteur: 5)
var c2 = c1
c2.hauteur = 8
Et si je compare les hauteurs :
c1.hauteur // 8
c2.hauteur // 8
Les deux hauteurs ont la même valeur ! Donc quand j'ai modifié ma référence à la deuxième instance, la première a été modifiée également. C'est toute la différence entre le type par référence et le type par valeur.
Avec la structure, quand j'écris var s2 = s1
, le programme copie toutes les valeurs de s1
et les mets dans un nouvel objet qui est affecté à s2
. Donc s1
et s2
représentent des objets différents. Donc quand s2
modifie ses propriétés, s1
n’est pas affecté.
Avec la classe, quand j'écris var c2 = c1
, le programme copie l'adresse de c2
et la passe à c1
. Donc c1
et c2
font référence au même objet. Donc quand c2 modifie ses propriétés, c1 subit les mêmes modifications.
La mutabilité
Faisons une deuxième expérience, déclarez une instance constante de notre classe et essayez de modifier ses propriétés.
let c = RectangleClass(largeur: 10, hauteur: 5)
c.hauteur = 12 // Pas de souci
Faisons la même chose avec la structure :
let s = RectangleStruct(largeur: 10, hauteur: 5)
s.hauteur = 12 // Erreur !
Cette fois, ça ne marche pas ! Une instance d'une structure déclarée avec let a ses propriétés constantes. Tout est constant et plus rien ne peut être modifié. Car les structures représentent directement leurs valeurs, donc si la structure est constante, toutes les valeurs contenues le sont aussi.
En revanche, une instance de classe est associée à une adresse donc si l'instance est constante, cela veut dire que l'adresse seulement est constante. On ne peut pas lui attribuer une nouvelle adresse, c'est-à-dire lui affecter un nouvel objet. Mais on peut modifier ses propriétés !
Différences pratiques
Les deux grandes différences entre les classes et les structures sont donc :
les structures n'ont pas d'héritage
les structures sont un type par valeur et les classes un type par référence
Hormis ces deux grandes différences, il existe quelques différences pratiques et je vais vous en citer deux principales :
1. L'initialisation par défaut
Si on reprend notre structure RectangleStruct
, on peut lui enlever ses valeurs par défaut et son initialisation et contrairement à la classe, cela ne déclenchera pas d'erreur :
struct RectangleStruct {
var hauteur: Int
var largeur: Int
}
La raison, c'est que les structures ont une petite fonctionnalité bien pratique. Elles sont équipées automatiquement d'un initialiseur par défaut qui prend en paramètre ses propriétés stockées. Autrement dit, sans avoir déclaré un initialiseur, je peux écrire :
var s = RectangleStruct(hauteur: 12, largeur: 3)
Pratique, non ?
2. Les fonctions mutantes
Une structure a, comme vous l'avez vu, un rapport direct avec ses valeurs. Et elle ne laisse pas n'importe qui les modifier ! Notamment si vous déclarer une méthode qui modifie une ou plusieurs propriétés de la structure, vous devez rajouter le mot-clé mutating
pour signifier à la structure que cette fonction risque de modifier certaines propriétés. Par exemple :
mutating func doublerLaTaille() {
hauteur *= 2
largeur *= 2
}
Utilisation
C'est le moment de la grande question : quand dois-je utiliser une structure plutôt qu'une classe ? Je vous propose une réponse simple :
Si votre modèle de donnée sert principalement à stocker des données et qu'il effectue peu de logique, choisissez une structure.
Donc si vous n'avez pas ou très peu de méthodes, une structure fera sans doute mieux l'affaire. C'est la raison pour laquelle j'ai choisi une structure pour notre type Question
.
Tous les types simples que vous connaissez : Int, Double, Float, String, Bool, Dictionnaire, Tableau sont des structures en Swift.
En résumé
Vous connaissez maintenant tous les modèles de données nommés de Swift : les classes, les structures et les énumérations. Bravo !
Une structure ressemble beaucoup à une classe avec 2 différences fondamentales :
les structures n'ont pas d'héritage.
les structures sont un type par valeur et les classes un type par référence.
Utiliser une structure lorsque vous cherchez principalement à stocker des données.