
L'héritage, c'est bien, mais ça a un petit défaut. On peut se mélanger un peu les types parfois. Laissez-moi vous donner un exemple. Prenons les classes suivantes :
class Media {
var titre: String
init(titre: String) {
self.titre = titre
}
}
class Film: Media {
var réalisateur: String
init(titre: String, réalisateur: String) {
self.réalisateur = réalisateur
super.init(titre: titre)
}
}
class Chanson: Media {
var chanteur: String
init(titre: String, chanteur: String) {
self.chanteur = chanteur
super.init(titre: titre)
}
}
Il y a donc trois classes. Les classes Chanson
et Film
héritent de la classe Media
. Avec cette structure de données, je peux créer le tableau de médias suivants :
let librairie = [
Film(titre: "Un balai dans le placard", réalisateur: "Rémi Movie"),
Chanson(titre: "L'ombre de ta valise", chanteur: "Frank Patatra"),
Chanson(titre: "Toi et moi dans le couloir", chanteur: "Johnny Les Vacances"),
Film(titre: "A portée de main", réalisateur: "Stanley Kubik"),
Film(titre: "Pourquoi pas ?", réalisateur: "Alfred Plicploc"),
Chanson(titre: "De si bon matin", chanteur: "Alain Chausson")
]
Ce tableau est parfaitement valable. En effet, il est du type [Media]
et il ne contient que des médias. Mais à cause de l'héritage, il y a maintenant deux types de médias ! Comment faire pour les différencier ? Par exemple, comment faire si je veux compter le nombre de chansons dans le tableau ?
Vérifier un type
Pour cela, nous allons découvrir ensemble le mot clé is
. Le mot clé is
permet d'inspecter le type d'un objet. Avec cet outil, essayons de compter le nombre de chansons dans la librairie :
var nombreDeChansons = 0
for media in librairie {
if media is Chanson { // Ici on contrôle que le media est bien une chanson
nombreDeChansons += 1
}
}
print("La librairie contient \(nombreDeChansons) chansons.")
Dans la boucle for, je ne sais pas si la variable media
est du type Chanson
ou du type Film
. La déclaration if media is Chanson
qui se traduit littéralement "si le média est une chanson" nous permet de vérifier le type du média. Si c'est le cas, on incrémente notre compteur.
Le mot-clé is
s'utilise donc avec la syntaxe variable is Type
et renvoie un booléen.
Accéder à un sous-type
En reprenant, notre exemple de librairie, j'aimerais maintenant afficher tous les chanteurs de toutes les chansons de la librairie. Essayons la même stratégie :
for media in librairie {
if media is Chanson {
print(media.chanteur) // ERREUR
}
}
On a une erreur, car même si on a contrôlé le type de media, Swift considère toujours que media est juste du type Media et pas du type Chanson. Et donc la propriété chanteur n'existe pas. C'est toute la rigueur de Swift, une même variable ne changera JAMAIS de type même si on prend beaucoup de précautions.
Il va donc falloir créer une nouvelle variable et on va faire cela en utilisant l'opérateur as
comme ceci :
for media in librairie {
if let chanson = media as? Chanson {
print(chanson.chanteur)
}
}
Voyons ce qu'il se passe en détail :
Si la variable
media
est du typeChanson
:Une nouvelle variable est créée : la variable
chanson
de typeChanson
et qui contient les mêmes données que la variablemedia
.On effectue les instructions à l'intérieur des accolades. La variable
chanson
y est disponible.
Si la variable
media
n'est pas du typeChanson
:Aucune variable n'est créée.
On ignore les instructions entre accolades.
Il nous reste une question à voir, celle du point d'interrogation. Le point d'interrogation après le as
signifie que cette opération peut échouer. En effet, la variable media peut ne pas être du type chanson.
On peut également utiliser le point d'exclamation après leas
si on est absolument certain du type de notre variable. Par exemple :
(librairie[1] as! Chanson).chanteur
Entre les parenthèses, on force l'évaluation de librairie[1]
à Chanson
, car on sait que c'est une chanson. Toute l'expression entre parenthèses est donc considérée comme une variable de type Chanson
dont on peut obtenir du coup la propriété chanteur
.
Utilisation
Vous avez donc 3 méthodes pour vérifier les types : is
, as?
et as!
. Alors, comment décider quelle méthode utiliser ? Suivez le guide !
Les types Any et AnyObject
Any
Il y a un type un petit peu particulier que nous allons découvrir maintenant : le type Any
. Une variable de type Any
peut accepter une valeur de n'importe quel type :
let a: Any = 12 // Ça marche
let b: Any = "Blob" // Ça marche aussi
let c: Any = true // Ça marche encore
let d: Any = Bus() // Ça marche toujours !
Encore plus fort, une variable de type Any
peut changer de valeur avec des valeurs de types différents.
var a: Any = 12
a = "Blob"
a = true
a = Bus()
AnyObject
Le type Any
a un petit frère que vous risquez de rencontrer : le type AnyObject
. Quelle est la différence ? Le type AnyObject
fonctionne uniquement avec les instances d'une classe. Cela exclut notamment tous les types basiques qui sont des structures en Swift (et non des classes) comme Int
, Float
, Double
, Bool
, String
. En résumé :
var a: AnyObject = 12 // Renvoie une erreur
var a: Any = 12 // OK
var b: AnyObject = Bus() // OK
var b: Any = Bus() // OK
Usages
OK, mais à quoi ça sert ?
Il y a deux usages principaux.
Collections de types mixés
Le premier usage courant, c'est de pouvoir créer des collections de types mixés par exemple comme ceci :
var tableauMixé: [Any] = [12, "Blob", true, Bus()]
Dans ce tableau, je peux utiliser des valeurs de différents types grâce au type Any
.
Objet de type inconnu
Le deuxième cas courant d'utilisation, c'est quand on ne sait pas le type d'un objet que l'on va utiliser.
Prenons l'exemple d'une requête réseau. Je souhaite récupérer l'âge d'un utilisateur. Le serveur peut me répondre de deux façons différentes :
Tout se passe bien, je reçois :
["age": 22]
. La réponse est donc du type :[String: Int]
.Il y a une erreur, je reçois : ["erreur": "Le serveur a planté !"]. La réponse est cette fois du type
[String: String]
.
Donc dans cet exemple, je peux recevoir une variable de deux types différents. Pour pouvoir gérer ces deux types, je vais déclarer que ma variable est du type :[String: Any]
.
Vérification du type
La raison pour laquelle je vous parle des types Any
et AnyObject
maintenant, c'est qu'avec des variables de ce type, on va souvent avoir besoin de vérifier le type qu'elles ont réellement avant de pouvoir les utiliser. Les types Any
et AnyObject
sont donc un des cas d'usage très courant de la vérification des types.
Exercice
Notre méthode drive
de SchoolBus
permet au bus de s'arrêter à chaque maison. Mais il ne récupère pas encore d'enfants. Dans cet exercice, vous allez rectifier ça :
À chaque maison, le bus récupère les enfants. Pour cela vous utiliserez la propriété
occupiedSeats
de la classeBus
et la propriétéchildren
de la classHomeRoadSection
.Le bus ne peut pas récupérer plus d'enfants que sa capacité de places assises (définie par la propriété
seats
).Arrivé à l'école, le bus dépose tous les enfants
// Ne regardez pas la correction
override func drive(road: Road) {
for section in road.sections {
switch section.type {
case .plain:
moveForward()
case .home:
if shouldPickChildren() {
pickChildren(from: section)
stop()
}
moveForward()
case .school:
dropChildren()
stop()
}
}
}
func shouldPickChildren() -> Bool {
return occupiedSeats < seats
}
func pickChildren(from roadSection: RoadSection) {
if let section = roadSection as? HomeRoadSection {
occupiedSeats += section.children
}
}
func dropChildren() {
occupiedSeats = 0
}
En résumé
Les deux cas d'usage les plus courants de la vérification des types sont : l'héritage et les types
Any
etAnyObject
.Pour vérifier un type, on a trois méthodes :
is
,as?
etas!
.Pour choisir la méthode à utiliser, vous pouvez vous référer à ce schéma :
Les types
Any
etAnyObject
permettent aux variables d'accepter des valeurs de n'importe quel type (uniquement les instances de classe pour le typeAnyObject
). On les utilise lorsqu'on veut faire des collections de types mixés ou qu'on ne connaît pas le type d'une variable que l'on va utiliser.