Fil d'Ariane
Mis à jour le vendredi 27 janvier 2017
  • 15 heures
  • Facile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Ce cours existe en eBook.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Les classes et les structures

Connectez-vous ou inscrivez-vous pour bénéficier de toutes les fonctionnalités de ce cours !

Voilà on y est, le moment est venu de créer vos propres classes pour déclarer vos objets tant attendus. 

Dans ce chapitre, on traduira ce que vous avez pu apprendre dans le chapitre précédent afin de créer votre première classe. Ensuite, nous verrons comment se servir d'une classe pour créer vos objets.

Créer une classe

Pour les exemples qui vont suivre, je vais garder l'exemple d'une personne. Je vais reprendre les mêmes caractéristiques présentées dans le chapitre précédent et en ajouter ou en supprimer selon les exemples.

Où placer sa/ses classe(s) dans notre code ?

Il est possible de placer vos classes directement dans votre fichiermain.swift. Seulement, vous verrez qu'une classe peut vite prendre de la place dans votre code. Et surtout, vous ne serez pas amené à créer un seul objet, mais plusieurs. Du coup, placer plusieurs classes au sein même de votre fichiermain.swift va rendre votre fichier gros et lourd à comprendre. C'est pourquoi je vous recommande de créer un nouveau fichier.swift par classe. Dans ce nouveau fichier, il n'y aura donc que la définition de votre classe – et rien d'autre.

Pour créer un nouveau fichier dans votre projet, vous devez faire un clic droit sur le nom du dossier qui porte le même nom que votre projet :

Création d'un nouveau fichier Swift (1/4)
Création d'un nouveau fichier Swift (1/4)

Cliquez sur New File.... On vous demandera le type de fichier à créer. Ici, ce qui nous intéresse est un fichier Swift. Dans la fenêtre qui est apparue, à gauche choisissez Source dans iOS ou OS X (peu importe, le fichier reste le même). Choisissez Swift File à droite.

Création d'un nouveau fichier Swift (2/4)
Création d'un nouveau fichier Swift (2/4)

On vous demandera alors de renseigner le nom de votre nouveau fichier. Par convention, on nomme le fichier du même nom que portera celle de votre classe. De plus, tout nom d'une classe doit commencer par une majuscule pour respecter la convention des programmeurs. Dans notre exemple, donnez comme nom de fichier :Personne.

Création d'un nouveau fichier Swift (3/4)
Création d'un nouveau fichier Swift (3/4)

Cliquez maintenant sur Create. Votre nouveau fichier est alors créé et visible dans le volet de gauche. Vous pouvez cliquer dessus pour accéder à ce fichier, et supprimer tout le contenu qui a été automatiquement généré.

Création d'un nouveau fichier Swift (4/4)
Création d'un nouveau fichier Swift (4/4)

Définir une classe

La définition d'une classe au sein d'un fichier se déclare par le mot-cléclass suivi de son nom et d'une paire d'accolades. C'est entre ces accolades que tout se passera : c'est là que l'on déclarera nos attributs et nos méthodes. Vous pouvez donc déjà écrire dans votre fichierPersonne.swift ce code :

class Personne {
    
}

Maintenant, nous allons pouvoir introduire nos attributs et nos méthodes. On place en général, là aussi par convention, dans un premier temps les attributs, et dans un second temps les méthodes. On gagne en lisibilité et en compréhension.

Les attributs

Restons simple et disons qu'une personne a pour le moment un nom, un prénom, un âge et une adresse.

class Personne {
    var nom: String
    var prenom: String
    var age: Int
    var adresse: String
}

Avec ce code, Xcode vous indique une erreur et vous ne pouvez pas lancer votre programme. En fait, il faut pouvoir définir des valeurs par défaut à une classe, c'est obligatoire. Soit directement lors de la déclaration des variables, soit lorsque l'on va instancier notre classe. Et cette méthode, je vous la décris plus bas. Donnons donc des valeurs à nos variables de telle sorte :

class Personne {
    var nom: String = "Dupont"
    var prenom: String = "Jean"
    var age: Int = 24
    var adresse: String = "94 rue machin"
}

Pas de piège ici, on agit exactement de la même façon qu'auparavant. Et vous allez voir que pour les méthodes, c'est aussi la même chose. 

Les méthodes

Maintenant que l'on a indiqué que notre classePersonne doit avoir un nom, un prénom, un âge et une adresse, définissons-lui des méthodes.

  • Une personne de sexe féminin peut être amenée à changer de nom après son mariage. Ah tiens ! Ça nous fait un nouvel attribut à notre objet.  :) 

  • Quand une personne fête son anniversaire, son âge a augmenté d'un an.

  • Quand une personne déménage, elle change d'adresse.

On pourrait donc définir des méthodes de ce genre :

class Personne {
    var nom: String = "Durand"
    var prenom: String = "Jeanne"
    var sexe = "f" // J'ajoute notre nouvel attribut
    var age: Int = 24
    var adresse: String = "94 rue machin"
    
    func seMarier() {
        
    }
    
    func feterSonAnniversaire() {
        
    }
    
    func demenager() {
        
    }
}

Ce serait bien maintenant qu'elle puisse réaliser ce que l'on souhaite. Pour cela, il va falloir accéder aux attributs de notre objet grâce au mot-cléself. Je vous présente le nouveau code, et j'explique ensuite.

class Personne {
    var nom: String = "Durand"
    var prenom: String = "Jeanne"
    var sexe = "f"
    var age: Int = 24
    var adresse: String = "94 rue machin"
    
    func seMarier(nouveauNom: String) {
        if self.sexe == "f" { // On peut changer de nom seulement si on est une fille
            self.nom = nouveauNom
        } else {
            print("Un garçon ne peut pas changer de nom de famille.")
        }
    }
    
    func feterSonAnniversaire() {
        self.age += 1 // On incrémente de 1 son âge
    }
    
    func demenager(nouvelleAdresse: String) {
        self.adresse = nouvelleAdresse
    }
}

 Ainsi, on accède aux attributs dans nos méthodes avec le mot-cléself.self.nom  signifie alors le nom de cet objet. Par la suite, si vous créez plusieurs objets de la même classe, ce mot-clé servira à préciser l'objet sur lequel on travaille et pas sur les autres.

La fonctionseMarier() prend un paramètre, le nouveau nom après le mariage. On vérifie bien que la personne est de sexe féminin pour pouvoir changer de nom. On change la valeur de son nom comme pour une variable normale.

La fonctionfeterSonAnniversaire() n'a pas besoin de paramètre. On a juste besoin d'augmenter son âge de 1 an, et son âge on le connaît déjà. Inutile donc de préciser le nouvel âge en paramètre : on incrémente juste de 1 son âge.

La fonctiondemenager() prend en paramètre la nouvelle adresse de sa maison. On accède à la valeur actuelle de son adresse, et on la change par sa nouvelle valeur passée en paramètre.

Comme pour les fonctions, il est aussi possible d'utiliser des méthodes définies dans votre classe dans d'autres méthodes de cette même classe. On aurait pu par exemple faire ceci :

class Personne {
    var nom: String = "Durand"
    var prenom: String = "Jeanne"
    var sexe = "f"
    var age: Int = 24
    var adresse: String = "94 rue machin"
    
    func seMarier(nouveauNom: String) {
        if personneEstDeSexeFeminin() {
            nom = nouveauNom
        } else {
            print("Un garçon ne peut pas changer de nom de famille.")
        }
    }
    
    // Nouvelle méthode qui teste si la personne est de sexe féminin
    func personneEstDeSexeFeminin() -> Bool {
        return sexe == "f"
    }
    
    func feterSonAnniversaire() {
        self.age += 1 // On incrémente de 1 son âge
    }
    
    func demenager(nouvelleAdresse: String) {
        self.adresse = nouvelleAdresse
    }
}

Penser objet, c'est penser à tout des petites astuces de ce genre qui vous feront gagner du temps, qui aideront à la lecture du code et qui surtout seront intéressantes à utiliser tout au long de votre code. La nouvelle fonction ici pourrait servir à d'autres endroits encore.

Créer un objet

Maintenant que nous avons défini notre classePersonne, on va pouvoir l'utiliser et la mettre en pratique.

Rendez-vous maintenant dans votre fichiermain.swift. Vous vous souvenez de ce que je vous avais dit ? Ce fichier représente le fichier principal. C'est votre point d'entrée dans votre programme. Quand vous lancez votre programme, c'est ce fichier qui va être lu et exécuté.

Déclarer un objet

Pour déclarer votre objetPersonne , voici comment faire :

let personne = Personne()

Vous affectez à votre variable le nom de votre classe suivi d'une paire de parenthèses (ouvrante et fermante). Swift va automatiquement savoir de lui-même où chercher ces informations et savoir quelle classe il faut instancier, c'est-à-dire ici dans le fichierPersonne.swift. Votre variablepersonne est maintenant un objet de typePersonne. Vous allez maintenant pouvoir gérer votre objet comme vous le souhaitez.

Utiliser un objet

Pour pouvoir gérer votre objet, vous allez d'abord préciser l'objet que vous souhaitez gérer. Ensuite, vous allez pouvoir indiquer les diverses actions à effectuer. Voici ce que l'on pourrait faire, par exemple.

let personne = Personne()

print("Nom de la personne au début : " + personne.nom)

// On peut changer une valeur de cette façon
personne.nom = "Dubois"
print("Le nouveau nom est maintenant : " + personne.nom)

// On peut réaliser des actions propres à la personne
print("Mon âge est \(personne.age) ans. Demain je fête mon anniversaire")
personne.feterSonAnniversaire()
print("J'ai maintenant \(personne.age) ans.")

personne.seMarier(nouveauNom: "Dupont")
print("Je me suis marié le jour de mon anniversaire aussi, tant qu'à faire. :)")
print("Voici mon nouveau nom : " + personne.nom)

print("Du coup, maintenant on a décidé de déménager. Actuellement j'habite à cette adresse : " + personne.adresse)
personne.demenager(nouvelleAdresse: "12 rue bidule")
print("J'habite dorénavant à cette adresse : " + personne.adresse)

Dans cette partie, on connaît le nom de l'objet que l'on a précisé dans une variable. Grâce à ce nom, on va pouvoir accéder aux attributs de l'objet ainsi qu'à ses méthodes.

  • personne.nom sera la variablenom de votre objetpersonne.

  • personne.feterSonAnniversaire() exécutera la fonction feterSonAnniversaire() de votre objetpersonne.

  • Etc.

J'ai commenté et placé desprint() afin de bien vous faire remarquer les changements de valeurs selon ce qu'on a réalisé, testez donc ce code. À vous maintenant d'ajouter de nouvelles méthodes, de nouveaux attributs et de jouer avec dans votre fichier main. N'hésitez pas à pratiquer pour bien comprendre comment ça marche.‌

Admettons que j'ajoute une méthodeaddition qui prend deux paramètres et qui retourne la somme de ces deux paramètres, voici ce que ça pourrait donner dans notre classe :

// ...
func addition(nb1: Int, nb2: Int) -> Int {
    return nb1 + nb2
}
// ...

Et voici comment l'utiliser dans notre main :

let personne = Personne()
var resultat = personne.addition(nb1: 1, nb2: 2)

Bien sûr, une méthode addition() n'a strictement rien à faire dans une classe Personne, c'est pour l'exemple. ;) 

L'avantage pour la POO est que maintenant, vous pouvez par exemple récupérer le fichier Personne.swift d'un de vos camarades et l'utiliser comme vous le souhaitez. Il suffit de connaître le nom des méthodes et des attributs, rien de plus. Tout ce qu'il a réalisé dans les méthodes, ça vous est égal, tant que ça fournit le résultat qu'il faut. D'ailleurs, regardez comment votre fichiermain.swift est simple à lire !

La surcharge de méthode

La surcharge de méthode consiste à déclarer une nouvelle méthode portant le même nom mais avec :

  • le même nombre de paramètres, mais au moins un type de ces paramètres est différent que celle de la méthode existante ;

  • un nombre de paramètres différent ;

  • les deux.

Pour vous expliquer cela, créons une nouvelle méthodeaffiche() qui affichera les valeurs de notre objet.

class Personne {
    // ...
    
    func affiche() {
        print("Nom : " + self.nom)
        print("Prénom : " + self.prenom)
        print("Sexe : " + self.sexe)
        print("Age : \(self.age)")
        print("Adresse : " + self.adresse)
    }
    
    // ...
}

Seulement, cette fonction affiche tout d'un coup. On ne veut peut-être pas forcément afficher tout, mais juste une valeur. On pourrait alors préciser ce qu'on souhaite afficher rien que par la présence d'un seul paramètre.

class Personne {
    // ...
    
    func affiche() {
        print("Nom : " + self.nom)
        print("Prénom : " + self.prenom)
        print("Sexe : " + self.sexe)
        print("Age : \(self.age)")
        print("Adresse : " + self.adresse)
    }
    
    func affiche(valeurAAfficher: String) {
        switch valeurAAfficher {
            case "Nom":
                print("Nom : " + self.nom)
                
            case "Prénom":
                print("Prénom : " + self.prenom)
            
            case "Sexe":
                print("Sexe : " + self.sexe)
            
            case "Age":
                print("Age : \(self.age)")
            
            case "Adresse":
                print("Adresse : " + self.adresse)
                
            default:
                print("Nous n'avons pas pu afficher ce que vous avez demandé.")
        }
    }
}

Nous avons maintenant deux méthodes affiche(). Une sans paramètre, et l'autre avec un paramètre : c'est-à-dire un nombre de paramètre différents. Testez ce code pour comprendre ce qui s'est passé :

let personne = Personne()

// Affichera tout
personne.affiche()

// Affichera juste ce que l'on a demandé
personne.affiche(valeurAAfficher: "Age")

Bien ! Maintenant, il nous reste un dernier point à voir que j'ai cité ci-dessus. Faire une surcharge de méthode mais en gardant le même nombre  de paramètres. Admettons que vous connaissez l'ordre des valeurs : numéro 1 pour afficher le nom, numéro 2 pour afficher le prénom, etc.

On peut alors dans ce cas-là réaliser une méthode qui prend cette fois-ci non pas unString en paramètre mais unInt en paramètre. Du coup, on pourrait avoir une nouvelle méthodeaffiche()   de ce genre.

class Personne {
    // ...
    
    func affiche() {
        print("Nom : " + self.nom)
        print("Prénom : " + self.prenom)
        print("Sexe : " + self.sexe)
        print("Age : \(self.age)")
        print("Adresse : " + self.adresse)
    }
    
    func affiche(valeurAAfficher: String) {
        switch valeurAAfficher {
            case "Nom":
                print("Nom : " + self.nom)
                
            case "Prénom":
                print("Prénom : " + self.prenom)
            
            case "Sexe":
                print("Sexe : " + self.sexe)
            
            case "Age":
                print("Age : \(self.age)")
            
            case "Adresse":
                print("Adresse : " + self.adresse)
            
            default:
                print("Nous n'avons pas pu afficher ce que vous avez demandé.")
        }
    }
    
    func affiche(valeurAAfficher: Int) {
        switch valeurAAfficher {
        case 1:
            print("Nom : " + self.nom)
            
        case 2:
            print("Prénom : " + self.prenom)
            
        case 3:
            print("Sexe : " + self.sexe)
            
        case 4:
            print("Age : \(self.age)")
            
        case 5:
            print("Adresse : " + self.adresse)
            
        default:
            print("Nous n'avons pas pu afficher ce que vous avez demandé.")
        }
    }
}

Et le main donnerait ceci :

let personne = Personne()

// Affichera tout
personne.affiche()

// Affichera juste ce que l'on a demandé
personne.affiche(valeurAAfficher: "Age")
personne.affiche(valeurAAfficher: 4)

Le constructeur

Je l'ai dit plus haut, pour déclarer un objet, il faut pouvoir fournir des valeurs à cet objet au départ. C'est pourquoi je vous ai fait mettre des valeurs par défaut à vos variables. Seulement, à chaque fois que vous allez créer un objet, il s'agira du même objet avec les mêmes valeurs. Il est possible cependant, lorsque l'on déclare un objet, de préciser directement les valeurs à attribuer à notre objet. On va utiliser pour cela un constructeur ou encore un initialiseur (initializer en anglais).

Le constructeur pour une classe ne sera rien d'autre qu'une fonction qui sera appelée dès lors que vous déclarez un nouvel objet. Elle sera toutefois appelée seulement si vous avez précisé sa méthode de construction. Voici comment faire :

class Personne {
    // On peut maintenant supprimer les valeurs par défaut
    var nom: String
    var prenom: String
    var sexe: String
    var age: Int
    var adresse: String
    
    // Constructeur
    init() {
        
    }
    
   // Autres méthodes de la classe
}

 

Déclaré comme ceci, ce constructeur ne fait rien. Il va falloir indiquer ce que l'on souhaite faire dès lors que l'on déclare un objet. Ici, nous voulons donner des valeurs à nos attributs. On peut alors faire ceci :

// ...
init() {
    self.nom = "Dupont"
    self.prenom = "Jean"
    self.sexe = "m"
    self.age = 20
    self.adresse = "94 rue machin"
}
// ...

Seulement, on revient au même problème initial. À chaque objet qu'on déclarera, on aura toujours au départ les mêmes valeurs. Et ce qu'on souhaite, c'est de pouvoir définir directement lors de la déclaration de l'objet, ses valeurs. 

Pour cela, il suffit de passer des paramètres à notre fonction init(). On passera en paramètre ce qu'on ne souhaite pas avoir par défaut. Donc ici tout. 

// ...
 init(nom: String, prenom: String, sexe: String, age: Int, adresse: String) {
    self.nom = nom
    self.prenom = prenom
    self.sexe = sexe
    self.age = age
    self.adresse = adresse
}
// ...

Grâce à cette méthode, la déclaration d'un nouvel objet se fera différemment.

let personne = Personne(nom: "Dupont", prenom: "Jean", sexe: "m", age: 20, adresse: "94 rue machin")

print("Nom : " + personne.nom)
print("Prénom : " + personne.prenom)
print("Sexe : " + personne.sexe)
print("Age : \(personne.age)")
print("Adresse : " + personne.adresse)

Comme pour les méthodes à plusieurs paramètres, on précise le nom de la variable d'abord, puis sa valeur. La première est cette fois-ci obligatoire.

Les structures

En plus des classes, Swift propose aussi les structures. Une structure c'est exactement même chose qu'une classe, on l'utilise exactement pareil. Les différences vont se faire :

  • Le mot clé ne sera pasclass  maisstruct.

  • La seule chose qu'une structure ne peut pas faire par rapport à une classe, c'est de bénéficier de l'héritage. L'héritage, c'est le sujet du prochain chapitre, ne vous inquiétez pas. Mais retenez bien le mot.

  • Les classes sont des types par référence et les structures des types par valeur.

Les deux premiers points sont assez clairs. Je vous indique tout de même la syntaxe d'une structure :

struct NomDeLaStructure {
    // Attributs et méthodes
}

 

Le dernier point doit sûrement vous titiller un peu l'esprit. Alors, je m'explique ! :p

Un type par référence c'est quoi ? Je vous fais tester un code et vous allez le constater de vous même.

var personne = Personne(nom: "Durand", prenom: "Paul", sexe: "m", age: 27, adresse: "94 rue machin")
var personne2 = personne

personne.age = 12

personne.affiche(valeurAAfficher: "Age")
personne2.affiche(valeurAAfficher: "Age")

Ce code affichera dans la console :

Age : 12
Age : 12

Par cet exemple, j'ai déclaré une première variable qui sera un objet de type Personne. J'ai ensuite déclaré une deuxième variable et j'ai dit que ce sera l'objet que contient la variable personne.

J'ai volontairement modifié l'âge en changeant la valeur de l'attribut grâce à personne.age = 12 . De cette façon, vous devriez vous attendre à ce que l'âge de personne soit 12 et l'âge de personne2 soit 27. Eh bien non ! C'est tout autre qui s'est affiché comme vous avez pu le constater. En effet, une classe est un type par référence. Cela veut dire que personne etpersonne2  sont exactement les mêmes objets que l'on manipule. Ainsi, effectuer une action surpersonne ou surpersonne2 , effectuera une action sur le même objet. Ce qui est appliqué àpersonne l'est aussi àpersonne2 .

Maintenant, qu'est-ce qu'un type par valeur ? On va créer une structure toute simple pour cela et refaire les mêmes tests que précédemment.

struct SuperHero {
    var nom: String
    var pouvoir: String
    
    func quiEstCe() {
        print("Nom : " + self.nom + ", Pouvoir : " + self.pouvoir)
    }
}

var flash = SuperHero(nom: "The Flash", pouvoir: "Se déplacer très rapidemment.")
var reverseFlash = flash

reverseFlash.nom = "Reverse Flash"

flash.quiEstCe()
reverseFlash.quiEstCe()

Par ce code, on obtiendra cette fois-ci en retour en console : 

Nom : The Flash, Pouvoir : Se déplacer très rapidemment
Nom : Reverse Flash, Pouvoir : Se déplacer très rapidemment

Vous pourrez apercevoir que cette fois-ci,reverseFlash  a simplement pris les valeurs de flash. Au final, ces deux-là seront deux héros totalement différents. Une action sur l'un ne le sera plus cette fois-ci sur l'autre.

Les structures sont utilisés dans des cas plus basiques que les classes. À vous de juger donc. ;)

Pour résumer

  • Une classe permet la définition d'un objet. 

  • Un objet peut avoir des attributs et des méthodes.

  • On place chaque nouvelle classe dans un nouveau fichier. Le nom du fichier doit être du même nom que notre classe et doit commencer par une majuscule.

  • Dans une classe, on accède aux variables de l'objet qui est actuellement en train d'être utilisé par le mot-cléself.

  • Un constructeur permet de préciser les actions à réaliser pour un objet dès lors qu'on le crée. On peut lui fournir des paramètres pour par exemple affecter des valeurs aux attributs de l'objet.

Exemple de certificat de réussite
Exemple de certificat de réussite