Salut tout le monde ! La dernière fois en décortiquant la structure d’un app iOS, on a parlé de classes dans le code. Eh bien il est temps de savoir un peu de quoi il s’agit et de comprendre comment ça marche. Si vous perdez un peu le nord, n’hésitez pas à relire l’article sur les fonctions avant de vous attaquer à celui-là, sinon à vos boussoles, on va s’orienter sur cette étape indispensable.
Le principe
Le principe de la POO (programmation orientée objet) et de définir dans le code des classes qui sont des objets contenant des fonctions et des variables qui lui sont propres. Par exemple, on pourrait imaginer un objet “Personnage” (dans un jeu) dans le code qui comprendrait plusieurs choses :
On regroupe ainsi dans un même bloc (un objet !) tout le code qui le concerne. Cela a plusieurs avantages non négligeables concernant l’organisation et la clarté de son code, mais cela permet aussi de pouvoir se servir plusieurs fois du même code en simultané et dans la même app sans avoir à le réécrire. Si on utilisait notre classe Personnage dans un jeu, on pourrait déclarer tous les personnages du jeu simplement avec un appel à notre classe.
Le code
Voyons tout ça avec du Swift, parce que je pense que ce sera plus clair à comprendre avec du vrai code (curieusement…). Déclarons notre personnage dans un playground Swift (iOS ou mac) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Personnage { var nom : String = "Bobby" var pts_vie : Int = 20 var position : [Int] = [0, 0] var attaque : Int = 3 func seDeplacer() { print("Déplacement") } func attaquer() { print("Attaquer") } func subiAttaque() { print("Attaque reçue") } } |
Les plus malins auront remarqué que le nom de la classe est écrit avec un majuscule au début, contrairement au noms de variables et de fonctions. C’est pour pouvoir les différencier lorsqu’on s’en sert dans le code. On verra dans un autre articles les bonnes pratiques de nomenclature en Swift. C’est d’ailleurs parce que ce sont des classes que les types par défaut des variables commencent tous par une majuscule (String, Int, Double…)
Tout ce code est inerte, c’est à dire qu’il ne s’exécutera pas tout seul de haut en bas comme on en a l’habitude. C’est plutôt comme un morceau de code autonome stocké là dont on peut se servir très facilement en l’appelant comme une fonction :
1 | let personnage = Personnage() |
A la différence qu’on ne stocke pas le résultat de la fonction dans la variable, mais le bloc de code lui-même. On peut ensuite accéder aux différentes composantes de l’objet en juxtaposant un point “.” à la suite de la variable puis en nommant la variable ou la fonction désirée :
1 2 3 4 | let personnage = Personnage() print(personnage.nom) // Bobby personnage.seDeplacer() // Déplacement |
On peut donc disposer d’objets indépendants et autonomes dans notre code que l’on peut réutiliser à volonté. On peut dans cet exemple déclarer plusieurs personnages qui auront tous les mêmes actions mais qui agiront indépendamment les uns des autres. On peut même les stocker dans un tableau par exemple :
1 2 3 4 5 | let pierre = Personnage() let paul = Personnage() let jacques = Personnage() var personnages : [Personnage] = [pierre, paul, jacques] |
Le type d’une classe par défaut est son nom, mais on verra plus tard qu’elle peut hériter d’autres types.
L’initialisation
Le problème qui se pose dans notre exemple, c’est qu’ici les trois variables différentes contiennent en fait toutes le même contenu. Par exemple si l’on souhaite lire le nom de paul, ce sera “Bobby” car c’est ce qui est défini par défaut dans notre classe. On peut bien sur le modifier comme une variable standard :
1 2 3 4 5 | let pierre = Personnage() print(pierre.nom) // Bobby pierre.nom = "Pierre" print(pierre.nom) // Pierre |
Notons d’ailleurs que l’on peut modifier librement les variables contenues dans notre classe même si celle-ci est stockée dans une constante (let
).
Mais ceci n’est pas très pratique, car il faut penser à chaque fois que l’on déclare une nouveau Personnage à modifier son nom juste après ou bien il s’appellera Bobby. On pourrait bien sûr stocker par défaut un String
vide dans la variable, mais cela ne résoudrai pas le problème de devoir effectuer la modification à chaque nouvelle déclaration.
On va donc pouvoir définir des variables qui n’ont pas de contenu tant que la classe n’est pas déclarée, elle ne contiendront pas de valeur par défaut. Pour cela rien de plus simple, il suffit de ne pas leur attribuer de valeur lors de leur déclaration :
1 2 3 4 5 6 | class Personnage { var nom : String var pts_vie : Int = 20 // etc... } |
Mais si on laisse la classe telle quelle, Xcode nous retournera une erreur Class ‘Personnage’ has no initializers comme quoi la classe n’a pas toutes ses variables initialisées. On va donc déclarer une fonction particulière qui va s’exécuter lors de l’initialisation de la classe : init()
1 2 3 4 5 6 7 8 9 10 11 12 | class Personnage { var nom : String var pts_vie : Int = 20 var position : [Int] = [0, 0] var attaque : Int = 3 init(nom _nom : String) { nom = _nom } // etc... } |
Le mot-clé
func
n’est pas requis pour cette fonction car elle occupe une place unique dans la classe et est interprétée différemment par le compilateur
De cette façon on pourra définir le nom de chaque personnage lors de leur déclaration, simplement en passant le paramètre “nom” à la classe :
1 2 3 | let pierre = Personnage(nom: "Pierre") let paul = Personnage(nom: "Paul") let jacques = Personnage(nom: "Jacques") |
Il ne sera en revanche plus possible d’initialiser la classe sans ce paramètre, pour ce faire on doit déclarer une autre instance de init()
sans paramètres, qui définisse une valeur par défaut de la variable nom :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Personnage { var nom : String var pts_vie : Int = 20 var position : [Int] = [0, 0] var attaque : Int = 3 init(nom _nom : String) { nom = _nom } init() { nom = "Bobby" } // etc... } let bobby = Personnage() let pierre = Personnage(nom: "Pierre") let paul = Personnage(nom: "Paul") let jacques = Personnage(nom: "Jacques") |
La désinitialisation
Tout comme on peut exécuter du code lors de l’initialisation d’une classe avec init(), on peut en exécuter de la même façon lors de sa désinitialisation avec deinit. On va pouvoir de cette façon s’occuper de terminer ou de ranger ce dont la classe s’occupe avant qu’elle ne soit détruite. Par exemple si classe communique avec le web, on va pouvoir lui dire de couper automatiquement la connexion lorsqu’elle est détruite, de cette façon on limitera les erreurs. Cette fois ci il ne s’agit pas vraiment d’une fonction étant donné qu’aucun paramètre ne peut y être passé mais son fonctionnement est le même :
1 2 3 4 5 6 7 8 9 10 | class Test { init() { print("Initialisation...") } deinit { print("Désinitialisation...") } } |
Plutôt simple non ? Oui mais alors je sens que vous vous posez une question, comment on désinitialise une classe ? Sujet délicat et assez complexe car pour bien le comprendre il nous faudra voir le fonctionnement de ARC (Automatic Reference Counting) et des pointeurs que l’on verra plus tard. Pour résumé, une classe se désinitialisera toute seule lorsqu’elle ne sera plus stockée et accessible par le reste du code. Par exemple, si on déclare une classe dans un tableau, puis qu’on vide ce tableau, plus aucune variable ne pointera vers elle, notre ami le Swift s’occupera donc de la désinitialiser :
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Test { init() { print("Initialisation...") } deinit { print("Désinitialisation...") } } var tableau = [Test()] // Initialisation... tableau = [] // Désinitialisation... |
Toujours là ? Bon on commence à avoir ce qu’il faut (et là où il faut) pour faire des scripts complexes et bien les organiser. On va s’arrêter là pour aujourd’hui, ne vous en faites pas on va y revenir très bientôt car c’est loin d’être tout ce qu’il y a à savoir sur ce mastodonte qu’est la POO ! Je vous dis donc à bientôt les amis 😉
Bonjour,
tu écris :
var personnages : [Personnage] = [pierre, paul, jacques]
on peut créer un tableau pour gagner du temps et mieux s’organiser ;
J’ai plusieurs questions : Doit-on forcement écrire chaque variables comme cela :
let pierre = Personnage()
let paul = Personnage()
let jacques = Personnage()
ou le tableau est la “traduction” de ça ?
et si on ne doit pas forcement le réécrire comment initialise t’on un tableau :
var personnages : [Personnage] = [pierre, paul, jacques]
Salut !
Désolé pour la réponse tardive, ça fait un moment (un an…) que je n’ai pas touché au blog et il serait temps de m’y remettre !
Oui il faut déclarer chaque variable indépendamment, le tableau n’est qu’un façon de les ranger en une liste. Ce qu’il est possible de faire c’est d’initialiser les objets directement dans le tableau sans passer par des variables comme ceci :
J’espère avoir répondu à ta question 😉