Comme les classes peuvent hériter les unes des autres (par exemple, CountrySinger
peut hériter de Singer
), cela signifie qu’une classe est en réalité un super-ensemble d’une autre : la classe B possède tout ce qu’a A, avec quelques extras. Ce qui signifie que vous pouvez traiter B comme de type B ou comme de type A, selon vos besoins.
Confus ? Essayons avec du code :
class Album {
var name: String
init(name: String) {
self.name = name
}
}
class StudioAlbum: Album {
var studio: String
init(name: String, studio: String) {
self.studio = studio
super.init(name: name)
}
}
class LiveAlbum: Album {
var location: String
init(name: String, location: String) {
self.location = location
super.init(name: name)
}
}
Nous définissons trois classes : les albums Album
, les albums de studio StudioAlbum
et les albums live LiveAlbum
, les deux derniers héritant de Album
. Parce que toute instance de LiveAlbum
est héritée de Album
, elle peut être traitée comme Album
ou LiveAlbum
- les deux en même temps. Cela s'appelle "polymorphisme", et cela signifie que vous pouvez écrire du code comme ceci :
var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")
var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]
Nous créons là un tableau qui ne contient que des albums, mais comprend deux albums studio et un album live. Cela convient parfaitement en Swift car ils sont tous issus de la classe Album
, ils partagent donc le même comportement de base.
Nous pouvons pousser cela un peu plus loin pour vraiment montrer comment fonctionne le polymorphisme. Ajoutons une méthode getPerformance()
aux trois classes :
class Album {
var name: String
init(name: String) {
self.name = name
}
func getPerformance() -> String {
return "The album \(name) sold lots"
}
}
class StudioAlbum: Album {
var studio: String
init(name: String, studio: String) {
self.studio = studio
super.init(name: name)
}
override func getPerformance() -> String {
return "The studio album \(name) sold lots"
}
}
class LiveAlbum: Album {
var location: String
init(name: String, location: String) {
self.location = location
super.init(name: name)
}
override func getPerformance() -> String {
return "The live album \(name) sold lots"
}
}
La méthode getPerformance()
existe dans la classe Album
, mais les deux classes enfant la remplacent. Lorsque nous créons un tableau contenant des Albums
, nous le remplissons en fait avec des sous-classes d'albums : LiveAlbum
et StudioAlbum
. Elles peuvent aller sans problèmes dans le tableau car elles héritent de la classe Album
, mais elles ne perdent jamais leur classe d'origine. Donc, nous pourrions écrire du code comme ceci :
var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")
var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]
for album in allAlbums {
print(album.getPerformance())
}
C'est la version de remplacement de getPerformance()
qui sera automatiquement utilisée en fonction de la sous-classe en question. C'est le polymorphisme en action : un objet peut fonctionner à la fois comme sa classe et ses classes parentes.
Vous constaterez souvent que vous avez un objet d'un certain type, alors que vous savez qu'il est vraiment d'un type différent. Malheureusement, si Swift n'est pas au courant de ce que vous savez, il ne compilera pas votre code. Il existe donc une solution, appelée typecasting (conversion de type) : convertir un objet d'un type en un autre type.
Il y a de fortes chances que vous ayez du mal à comprendre pourquoi cela pourrait être nécessaire, mais je peux vous donner un exemple très simple :
for album in allAlbums {
print(album.getPerformance())
}
C'était notre boucle d'il y a quelques minutes. Le tableau allAlbums
contient le typeAlbum
, mais nous savons qu'il contient l'une des sous-classes : StudioAlbum
ou LiveAlbum
. Swift ne le sait pas, donc si vous essayez d'écrire quelque chose comme print(album.studio)
, il refusera de compiler le code car seuls les objets StudioAlbum
possèdent cette propriété.
La conversion de type de données en Swift se présente sous trois formes, mais la plupart du temps, vous n'en rencontrerez que deux : as?
et as!
, connues sous le nom de downcasting optionnel et de downcasting forcé. Le premier signifie "Je pense que cette conversion est peut-être vraie, mais cela pourrait échouer", et le second signifie "Je sais que cette conversion est vraie et je suis heureux que mon application plante si je me trompe".
Remarque : lorsque je dis "conversion", je ne veux pas dire que l'objet est littéralement transformé. Au lieu de cela, il s'agit simplement de convertir la manière dont Swift traite l'objet - vous dites à Swift qu'un objet qu'il pensait être du type A est en réalité du type E.
Les points d'interrogation et d'exclamation devraient vous donner une idée de ce qui se passe, car cela ressemble beaucoup au territoire des optionnels. Par exemple, si vous écrivez ceci :
for album in allAlbums {
let studioAlbum = album as? StudioAlbum
}
Swift fera en sorte que studioAlbum
ait le type de données StudioAlbum?
, c’est-à-dire un album studio optionnel : la conversion a peut-être fonctionné, auquel cas vous avez un album studio avec lequel vous pouvez travailler, ou elle a peut-être échoué, auquel cas vous n’avez rien.
Ceci est le plus souvent utilisé avec if let
pour déballer automatiquement le résultat optionnel, comme ceci :
for album in allAlbums {
print(album.getPerformance())
if let studioAlbum = album as? StudioAlbum {
print(studioAlbum.studio)
} else if let liveAlbum = album as? LiveAlbum {
print(liveAlbum.location)
}
}
Cette boucle va parcourir chaque album et afficher les détails de ses ventes (getPerformance()), car cette méthode est communne à la classe Album
et à toutes ses sous-classes. Elle vérifie ensuite si elle peut convertir la valeur album
en un StudioAlbum
, et si elle le peut, afficher le nom du studio. La même chose est faite pour le type LiveAlbum
dans le tableau.
Vous pouvez utiliser le downcasting forcé lorsque vous êtes vraiment sûr qu'un objet d'un type peut être traité comme un objet de type différent, mais si vous vous trompez, votre programme plantera. Le downcasting forcé n'a pas besoin de renvoyer une valeur optionnelle, car vous dites que la conversion fonctionnera sans aucun doute. Si vous vous trompez, cela signifie que vous avez mal écrit votre code.
Pour illustrer cela, ignorons le type LiveAlbum
afin qu'il ne reste que des albums studio dans le tableau :
var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var allAlbums: [Album] = [taylorSwift, fearless]
for album in allAlbums {
let studioAlbum = album as! StudioAlbum
print(studioAlbum.studio)
}
C'est évidemment un exemple arrangé, parce que si c'était vraiment votre code, vous changeriez simplement allAlbums
pour qu'il ait le type de données [StudioAlbum]
. Néanmoins, il montre comment fonctionne le downcasting forcé et l'exemple ne plante pas car il pose des hypothèses correctes.
Swift vous permet de faire un downcast directement dans l'instruction de la boucle du tableau, ce qui dans ce cas serait plus efficace. Si vous voulez écrire ce downcast forcé au niveau du tableau, écrivez ceci :
for album in allAlbums as! [StudioAlbum] {
print(album.studio)
}
Il n'y a plus besoin de déballer chaque élément de la boucle, car cela se produit lorsque celle-ci commence. Encore une fois, vous feriez mieux de préciser que tous les éléments du tableau sont de type StudioAlbums
, sinon votre code pourrait planter.
Swift permet également le downcasting optionnel au niveau du tableau, bien que cela soit un peu plus délicat, car vous devez utiliser l'opérateur nil coalescing pour vous assurer qu'il y a toujours une valeur pour la boucle. Voici un exemple :
for album in allAlbums as? [LiveAlbum] ?? [LiveAlbum]() {
print(album.location)
}
Ce que cela signifie, c’est “essaye de convertir allAlbums
en un tableau d’objets de type LiveAlbum
, mais si cela échoue, créé simplement un tableau vide d’albums live et utilise-le à la place", c’est-à-dire ne fais rien.
Le typecasting est utile lorsque vous savez quelque chose que Swift ignore, par exemple lorsque vous avez un objet de type A
qui selon Swift est en réalité de type B
. Toutefois, il n’est utile que lorsque ces types correspondent vraiment à vos attentes - vous ne pouvez pas forcer un type A
à être un type Z
s’ils ne sont pas réellement liés.
Par exemple, si vous avez un entier appelé nombre
, vous ne pouvez pas écrire un code comme celui-ci pour en faire une chaîne de caractères :
let number = 5
let text = number as! String
Autrement dit, vous ne pouvez pas forcer un entier à être une chaîne de caractères, car ce sont deux types complètement différents. Au lieu de cela, vous devez créer une nouvelle chaîne en lui fournissant l'entier, et Swift sait comment convertir les deux. La différence est subtile : il s'agit d'une nouvelle valeur, plutôt que d'une simple réinterprétation de la même valeur.
Donc, ce code devrait être réécrit comme ceci :
let number = 5
let text = String(number)
print(text)
Cela ne fonctionne que pour certains types de données intégrés à Swift : vous pouvez par exemple convertir des entiers et des flottants en chaînes de caractères, mais si vous avez créé deux structures personnalisées, Swift ne peut pas en convertir une par magie en une autre - vous devez écrire ce code vous-même.
SPONSORED Alex is the iOS & Mac developer’s ultimate AI assistant. It integrates with Xcode, offering a best-in-class Swift coding agent. Generate modern SwiftUI from images. Fast-apply suggestions from Claude 3.5 Sonnet, o3-mini, and DeepSeek R1. Autofix Swift 6 errors and warnings. And so much more. Start your 7-day free trial today!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.