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 :
Ces trois différences sont importantes, je vais donc les approfondir avant de continuer.
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.
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 :
Singer
n'a pas de propriété noiseLevel
.HeavyMetalSinger
qui accepte un niveau de bruit.name
et "l'âge" age
du chanteur de heavy metal, afin de pouvoir le transmettre à la super-classe Singer
.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.
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.
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 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.
SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Link copied to your pasteboard.