NEW: Learn SwiftUI with my free YouTube video series! >>

< Previous: Structures (Structs)   Next: Propriétés >

Classes

Swift a une autre façon de construire des types de données complexes appelés classes. Elles ressemblent aux structures, mais présentent un certain nombre de différences importantes, notamment :

  • Vous n'avez pas d'initialiseur automatique d'instances pour vos classes ; vous devez écrire le vôtre.
  • Vous pouvez définir une classe comme étant basée sur une autre classe, en ajoutant les nouveaux éléments dont vous avez besoin.
  • Lorsque vous créez une instance d’une classe, elle est appelée un objet. Si vous copiez cet objet, les deux copies pointent par défaut sur les mêmes données. Changez-en une et la copie change également.

Ces trois différences sont importantes, je vais donc les approfondir avant de continuer.

Initialiser un objet

Si nous devions convertir notre structure Person en une classe Person, Swift ne nous laisserait pas écrire ceci :

class Person {
    var clothes: String
    var shoes: String
}

C'est parce que nous déclarons les deux propriétés comme étant de type String, ce qui, si vous vous en souvenez, signifie qu'elles doivent absolument avoir une valeur. Cela fonctionnait bien dans une structure, car Swift génère automatiquement un initialiseur pour nous ce qui nous oblige à fournir des valeurs pour les deux propriétés lors de la création d'une instance, mais cela ne se produit pas avec les classes. Swift ne peut donc pas être sûr qu'elles recevront bien des valeurs.

Il existe trois solutions : modifier les deux propriétés pour qu'elles soient des chaînes de caractères optionnelles, leur attribuer des valeurs par défaut ou écrire notre propre initialiseur. La première option est maladroite car elle introduit des optionnels partout dans notre code là où ils ne sont pas nécessaires. La deuxième option fonctionne, mais c'est un peu inutile, à moins que ces valeurs par défaut ne soient réellement utilisées. Il reste la troisième option, et c'est vraiment la bonne : écrire notre propre initialiseur.

Pour faire ceci, créez une méthode dans la classe appelée init() qui prend les deux paramètres qui nous intéressent :

class Person {
    var clothes: String
    var shoes: String

    init(clothes: String, shoes: String) {
        self.clothes = clothes
        self.shoes = shoes
    }
}

Il y a deux choses qui pourraient vous échapper dans ce code.

Tout d'abord, vous n'écrivez pas func devant votre méthode init(), car elle est spéciale. Deuxièmement, comme les noms des paramètres transmis sont les mêmes que ceux des propriétés auxquelles nous souhaitons attribuer des valeurs, vous utilisez self. pour préciser - "la propriété clothes de cet objet doit être définie sur le paramètre clothes qui a été passé." Vous pouvez leur donner des noms uniques si vous le souhaitez - c'est à vous de décider.

Important : Swift exige que toutes les propriétés qui ne sont pas optionnelles aient une valeur à la fin de l'initialiseur ou au moment où l'initialiseur appelle une autre méthode - peu importe l'ordre dans lequel cela intervient.

Héritage de classe

La deuxième différence entre les classes et les structures réside dans le fait que les classes peuvent s'appuyer les unes sur les autres pour produire de plus grandes choses, on appelle cela l'héritage de classe. C’est une technique très utilisée dans Cocoa Touch, même dans les programmes les plus élémentaires. C’est donc quelque chose que vous devez maîtriser.

Commençons par quelque chose de simple : une classe Singer (Chanteur) qui a des propriétés, leur nom et leur âge. En ce qui concerne les méthodes, il y aura un simple initialiseur pour attribuer des valeurs aux propriétés, plus une méthode sing() qui affiche quelques mots :

class Singer {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func sing() {
        print("La la la la")
    }
}

Nous pouvons maintenant créer une instance de cet objet en appelant cet initialiseur, puis en lire les propriétés et appeler sa méthode :

var taylor = Singer(name: "Taylor", age: 25)
taylor.name
taylor.age
taylor.sing()

C'est notre classe de base, mais nous allons continuer sur cette lancée : je veux définir une classe CountrySinger qui a tout ce que fait la classe Singer, mais quand j'appelle sing(), je veux afficher "Trucks, guitars, and liquor" (Camions, guitares et alcool) à la place.

Vous pourriez bien sûr simplement copier et coller l'original Singer dans une nouvelle classe appelée CountrySinger, mais c'est une façon paresseuse de programmer et elle reviendra vous hanter si vous apportez des modifications ultérieures à Singer et oubliez de les copier dans CountrySinger. Au lieu de cela, Swift propose une solution plus intelligente : nous pouvons définir CountrySinger comme étant basé sur Singer et nous obtiendrons toutes ses propriétés et méthodes sur lesquelles nous pourrons nous appuyer :

class CountrySinger: Singer {

}

Ce deux points est ce qui fait la magie : cela signifie "CountrySinger étend Singer." Maintenant, cette nouvelle classe CountrySinger (appelée une sous-classe) n’ajoute rien pour le moment à Singer (appelée classe parente ou super-classe). Nous voulons qu'elle ait sa propre méthode sing(), mais en Swift, vous devez apprendre un nouveau mot-clé : override. Ça signifie "Je sais que cette méthode a été implémentée par ma classe parente, mais je souhaite la modifier pour cette sous-classe."

Avoir le mot-clé override est utile, car cela clarifie votre intention. Cela permet également à Swift de vérifier votre code : si vous n’utilisez pas override, Swift ne vous laissera pas modifier une méthode que vous avez obtenue de votre superclasse, ou si vous utilisez override et qu’il n’y a rien à remplacer, Swift soulignera votre erreur.

Donc, nous devons utiliser override func, comme ceci :

class CountrySinger: Singer {
    override func sing() {
        print("Trucks, guitars, and liquor")
    }
}

Maintenant modifiez la manière dont l'objet taylor est créé :

var taylor = CountrySinger(name: "Taylor", age: 25)
taylor.sing()

Si vous changez CountrySinger en Singer, vous devriez pouvoir voir les différents messages apparaître dans le volet des résultats.

Maintenant, pour rendre les choses plus compliquées, nous allons définir une nouvelle classe appelée HeavyMetalSinger. Mais cette fois, nous allons stocker une nouvelle propriété appelée "noiseLevel" qui définit le niveau sonore de ce chanteur de heavy metal particulier qui aime crier dans son micro.

Cela pose un problème qui doit être résolu de manière très particulière :

  • Swift veut que toutes les propriétés non optionnelles aient une valeur.
  • Notre classe Singer n'a pas de propriété noiseLevel.
  • Nous devons donc créer un initialiseur personnalisé pour HeavyMetalSinger qui accepte un niveau de bruit.
  • Ce nouvel initialiseur doit également connaître le "nom" name et "l'âge" age du chanteur de heavy metal, afin de pouvoir le transmettre à la super-classe Singer.
  • La transmission des données à la super-classe se fait via un appel de méthode. Vous ne pouvez pas effectuer d'appel de méthode dans les initialiseurs tant que vous n'avez pas assigné des valeurs à toutes vos propriétés.
  • Nous devons donc définir d’abord notre propre propriété (noiseLevel), puis transmettre les autres paramètres que la super-classe utilisera.

Cela peut sembler terriblement compliqué, mais dans le code, c'est simple. Voici la classe HeavyMetalSinger, complète avec sa propre méthode sing() :

class HeavyMetalSinger: Singer {
    var noiseLevel: Int

    init(name: String, age: Int, noiseLevel: Int) {
        self.noiseLevel = noiseLevel
        super.init(name: name, age: age)
    }

    override func sing() {
        print("Grrrrr rargh rargh rarrrrgh!")
    }
}

Notez que son initialiseur prend trois paramètres, puis appelle super.init() pour transmettre name et age à la super-classe Singer - mais seulement après qu'une valeur ait été assignée à sa propre propriété. Vous verrez que super est beaucoup utilisé lorsque vous travaillez avec des objets, et cela signifie simplement "appele une méthode de la classe dont j'ai hérité." Ce qui signifie généralement "laisse ma classe parente faire tout ce qu'elle doit faire en premier, puis je ferai ce que j'ai à faire ensuite".

L'héritage de classe est un sujet important, alors ne vous inquiétez pas si ce n'est pas encore clair. Cependant, il vous reste une dernière chose à savoir : l'héritage de classe s'étend souvent sur plusieurs niveaux. Par exemple, A peut hériter de B, et B peut hériter de C, et C peut hériter de D, etc. Cela vous donne la possibilité de créer des fonctionnalités et de les réutiliser dans plusieurs classes, ce qui permet de garder votre code modulaire et facile à comprendre.

Travailler avec du code Objective-C

Si vous souhaitez qu'une partie du système d'exploitation d'Apple appelle une méthode de votre classe Swift, vous devez la marquer avec un attribut spécial : @objc. C'est l'abréviation de "Objective-C" et l'attribut marque effectivement la méthode comme étant disponible pour les anciens codes à exécuter en Objective-C - ce qui correspond à la quasi-totalité d'iOS, de macOS, watchOS et tvOS. Par exemple, si vous demandez au système d’appeler votre méthode au bout d’une seconde, vous devez le marquer avec @objc.

Ne vous inquiétez pas trop à propos de @objc pour l'instant - non seulement je vais l'expliquer dans un contexte plus tard, mais Xcode vous dira toujours quand il est nécessaire. Sinon, si vous ne voulez pas utiliser @objc pour des méthodes individuelles, vous pouvez mettre @objcMembers devant votre classe pour que toutes ses méthodes soient automatiquement disponibles pour Objective-C.

Valeurs par rapport à références

Lorsque vous copiez une structure, tout est dupliqué, y compris toutes ses valeurs. Cela signifie que changer une copie d'une structure ne change pas les autres copies - elles sont toutes individuelles. Avec les classes, chaque copie d'un objet pointe sur le même objet d'origine. Par conséquent, si vous en modifiez un, ils changent tous. Swift appelle les structures "types de valeur" car elles pointent simplement sur une valeur et les classes "types de référence" car les objets ne sont que des références partagées de la valeur réelle.

C'est une différence importante, et cela signifie que le choix entre structures (structs) et classes est important :

  • Si vous voulez avoir un état partagé qui est transmis et modifié sur place, vous utiliserez des classes. Vous pouvez les transmettre dans des fonctions ou les stocker dans des tableaux, les modifier et faire en sorte que cette modification soit reflétée dans le reste de votre programme.
  • Si vous voulez éviter un état partagé dans lequel une copie ne peut pas affecter toutes les autres, vous utiliserez des structures. Vous pouvez les passer dans des fonctions ou les stocker dans des tableaux, les modifier, et elles ne changeront pas où qu'elles soient référencées.

Si je devais résumer cette différence essentielle entre les structures et les classes, je dirais ceci : les classes offrent plus de flexibilité, tandis que les structures offrent plus de sécurité. En règle générale, vous devez toujours utiliser des structures à moins que vous ayez une raison spécifique d'utiliser des classes.

LEARN SWIFTUI FOR FREE I wrote a massive, free SwiftUI tutorial collection, and also have a growing list of free SwiftUI tutorials on YouTube – get started today!

< Previous: Structures (Structs)   Next: Propriétés >
MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let me know!

Click here to visit the Hacking with Swift store >>