Pour le moment, vous connaissez deux types de propriétés :
les propriétés d'instance ;
les propriétés de classe (ou statiques).
Qu'elles soient statiques ou d'instance, toutes les propriétés que vous avez vues pour le moment sont ce qu'on appelle des propriétés stockées. Cela veut dire qu'elles sont associées à une valeur qui est stockée en mémoire.
Il existe en Swift un autre type de propriété : les propriétés calculées. Et c'est ce que nous allons voir dans ce chapitre.
Comprenez le concept des propriétés calculées
Les propriétés calculées diffèrent des propriétés stockées ainsi :
les propriétés stockées sont associées à une valeur ;
les propriétés calculées sont associées à un calcul.
Un calcul ? C'est-à-dire ?
En fait, si on y réfléchit, une propriété admet deux actions :
une action pour récupérer la valeur contenue, on va appeler cette action get (récupérer, en anglais) ;
une action pour modifier la valeur contenue, on va appeler cette action set (modifier, en anglais).
Dans une propriété stockée, ces actions ont lieu automatiquement. Dans une propriété calculée, on va pouvoir modifier les actions get et set.
Implémentez des propriétés calculées
Rien de tel qu'un bon exemple pour voir comment tout ça fonctionne. Je vous présente la classe Square
suivante, qui permet de définir un carré.
class Square {
var length = 1
}
J'aimerais bien ajouter une propriété perimeter
à ma classe, qui me permettrait d'obtenir le périmètre de la classe. Le périmètre d'un carré c'est 4 fois la longueur, donc allons-y !
class Square {
var length = 1
var perimeter = 4
}
Hmm... C'est bien, mais le problème, c'est que si je modifie la longueur de mon carré, mon périmètre ne sera plus exact. On voit apparaître la limite des propriétés stockées. Je vais donc vous montrer comment créer une propriété calculée.
Transformons donc notre périmètre en propriété calculée :
class Square {
var length = 1
var perimeter: Int {
get {
return 4
}
set {
}
}
}
Voici la forme basique d'une propriété calculée. Pour l'instant elle a exactement le même rôle qu'avant. Détaillons un peu ce qui s'y trouve :
tout d'abord il faut indiquer le type de la propriété. Comme je ne lui donne plus de valeur, il faut lui préciser le type explicitement ;
ensuite j'ouvre des accolades, un peu comme pour une fonction ;
à l'intérieur des accolades se trouvent deux sortes de sous-fonctions :
get : c'est l'action qui permet de récupérer une valeur, on appelle ça le getter. Elle se comporte exactement comme une fonction, avec une valeur de retour de type
Int
. Pour l'instant, elle renvoie simplement 4,set : c'est l'action qui permet de modifier notre valeur, on appelle ça le setter. Pour l'instant, cette fonction est vide. Telle quelle, elle ne change rien à ce qui se passe lorsqu'on modifie la valeur de la variable
perimeter
.
Commençons par modifier notre getter :
var perimeter: Int {
get {
return length * 4
}
set {
}
}
De cette façon, le périmètre sera toujours égal à 4 fois la longueur :
let c = Square()
c.length = 4
c.perimeter // Affiche 16
Le problème maintenant, c'est lorsqu'on modifie le périmètre :
let c = Square()
c.perimeter = 8
c.length // Affiche 1
La longueur ne s'adapte pas quand le périmètre change ; pourtant les deux doivent toujours être reliés.
Je vais donc modifier le setter pour qu'il modifie la longueur à chaque fois que le périmètre est modifié :
var perimeter: Int {
get {
return length * 4
}
set {
length = newValue / 4
}
}
Le setter se comporte exactement comme une fonction qui a pour paramètre newValue
. newValue
contient la nouvelle valeur que l'on est en train de donner au périmètre. Par exemple, si j'écris :
let c = Square()
c.perimeter = 8
Dans ce cas, newValue
vaut 8 ; et donc maintenant si j'affiche la longueur, j'obtiens bien 8 / 4 = 2 :
c.length // Affiche 2
Dans le setter, la propriété newValue
contient la nouvelle valeur. Et la propriété perimeter
contient l'ancienne valeur. Cela peut être pratique si on souhaite comparer les deux valeurs.
En résumé :
Le getter se comporte comme une fonction avec une valeur de retour du type de la propriété. Il est appelé lorsqu'on veut récupérer la valeur de la propriété.
Le setter se comporte comme une fonction avec un paramètre
newValue
du type de la propriété. Il est appelé lorsqu'on modifie la valeur de la propriété.
Parfois nous écrivons des propriétés que nous n'avons pas besoin de modifier. Nous ne pouvons pas pourtant en faire des constantes, car leur valeur peut varier en fonction des autres valeurs. Par exemple, ajoutons une propriété description à notre Square
.
class Square {
let description = "Je suis un carré"
// (...)
}
C'est bien, mais c'est un peu générique. Essayons d'afficher plutôt "Je suis un carré de longueur X". Il va nous falloir une propriété calculée :
var description: String {
get {
return "Je suis un carré de longueur \(length)"
}
set {
}
}
Voilà qui fonctionne comme on le souhaite. Mais on veut aller plus loin. On ne veut pas que quelqu'un puisse modifier la description, car il pourrait écrire n'importe quoi et notre description ne serait plus vraie.
On va transformer cette propriété calculée en propriété calculée en lecture seule, comme ceci :
var description: String {
get {
return "Je suis un carré de longueur \(length)"
}
}
Il suffit d'effacer le setter, et cette propriété ne peut plus être modifiée ! On dit qu'elle est en lecture seule. Je ne peux donc plus écrire :
let c = Square()
c.description = "N'importe quoi" // error: 'description' is a get-only property
var description: String {
return "Je suis un carré de longueur \(length)"}
Appréhendez quelques règles des propriétés calculées
Maintenant que vous avez bien compris ce que sont les propriétés calculées, je vais vous annoncer quelques règles ou remarques à leur sujet.
Les propriétés calculées ne sont jamais constantes. Autrement dit, on ne peut pas les déclarer avec
let
. Leur valeur est calculée ; donc, par définition, elle est appelée à changer.Les propriétés calculées ont toujours un getter. Au minimum, une propriété, ça renvoie une valeur, donc il faut toujours pouvoir le faire :
// OK il y a un getter
var calculatedProperty: Type {
get {
return something
}
set {
}
}
// OK il y a un getter, la propriété est en lecture seule
var calculatedProperty: Type {
get {
return something
}
}
// OK la propriété est en lecture seule, le getter est directement dans les accolades
var calculatedProperty: Type {
return something
}
// FAUX ! Il n'y a pas de getter, c'est interdit
var calculatedProperty: Type {
set {
}
}
// ARCHI FAUX ! Il n'y a rien du tout
var calculatedProperty: Type {
}
C'est tout pour les propriétés calculées !
Observez les propriétés
Il existe un moyen d'observer les propriétés en Swift.
Observer les propriétés, mais qu'est-ce que c'est encore que cette histoire ?
Vous allez voir que c'est assez proche de ce qu'on a déjà vu. Observer une propriété permet d'effectuer une action lorsque la propriété est modifiée.
Hé, mais c'est exactement comme un setter !
Eh oui, exactement ! Dans notre exemple du carré, la méthode set
nous permettait de modifier la longueur dès que le périmètre était modifié. Le seul souci là-dedans, c'est queset
ne fonctionne qu'avec des propriétés calculées.
Il existe deux méthodes pour écouter des propriétés stockées :
willSet
: cette méthode est appelée juste avant la modification de la propriété ;didSet
: cette méthode est appelée juste après la modification de la propriété.
Prenons un exemple et essayons d'observer la propriété longueur :
var length = 1 {
willSet {
print("Le carré va changer de taille")
}
didSet {
if oldValue > length {
print("Le carré a rapetissé")
} else {
print("Le carré a grandi")
}
}
}
Dans la méthode willSet
, on affiche que le carré va changer de taille, car cette méthode est appelée avant la modification.
Dans la méthode didSet
, on compare l'ancienne valeur contenue dans la variable oldValue
à la nouvelle valeur contenue dans length
. En fonction du résultat, on affiche que le carré a rapetissé ou grandi. Cette méthode est appelée après la modification.
De façon similaire à oldValue
, la méthode willSet
a une variable newValue
qui contient la nouvelle valeur. Le tableau ci-dessous résume cela :
Donc en résumé, pour observer la modification d'une propriété on utilise :
set
: pour les propriétés calculées ;willSet
etdidSet
: pour les propriétés stockées.
Nous avons vu dans ce chapitre un certain nombre de notions assez avancées sur les propriétés. Pour vous récompenser pour votre persévérance et vous donner une vue d'ensemble des propriétés, je vous ai concocté un petit schéma. N'hésitez pas à y revenir en cas de doute.
À vous de jouer !
Exercice 1
Observez la propriété occupiedSeats
de Bus
. Avant sa modification, faites afficher la phrase "Il y a du mouvement dans le bus...". Après sa modification, faites afficher la phrase "X personnes viennent de monter !" ou "X personnes viennent de descendre !" selon que la valeur de la propriété augmente ou baisse.
Exercice 2
Ajoutez une propriété calculée en lecture seule au Bus. Cette propriété s'appellera description
, et contient "Je suis un bus conduit par X avec Y personnes dedans.". X est le nom du chauffeur et Y est le nombre de personnes dans le bus.
En résumé
On peut diviser les propriétés en deux types : les propriétés stockées et les propriétés calculées. Parmi les propriétés calculées, on peut créer des propriétés calculées en lecture seule.
Une propriété calculée permet de modifier les getters et setters au moyen des méthodes
get
etset
.Une propriété calculée n'est jamais constante, et a toujours un getter.
Une propriété stockée peut être observée grâce aux méthodes
willSet
etdidSet
.
Maintenant que vous savez tout sur les setters et les getters de la POO, je vous invite à aller plus loin sur la notion de l’initialisation !