• 8 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 10/10/2023

Contrôlez vos types

Contrôlez vos types

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 title: String
   init(title: String) {
      self.title = title
   }
}

 

class Movie: Media {
   var director: String
   init(title: String, director: String) {
      self.director = director
      super.init(title: title)}
   }
}

 

class Song: Media {
   var singer: String
   init(title: String, singer: String) {
      self.singer = singer
      super.init(title: title)
   }
}

Il y a donc trois classes. Les classes Song et Movie héritent de la classe Media . Avec cette structure de données, je peux créer le tableau de médias suivants :

let library: [Media] = [
   Movie(title: "Un balai dans le placard", director: "Rémi Movie"),
   Song(title: "L'ombre de ta valise", singer: "Frank Patatra"),
   Song(title: "Toi et moi dans le couloir", singer: "Johnny Les Vacances"),
   Movie(title: "A portée de main", director: "Stanley Kubik"),
   Movie(title: "Pourquoi pas ?", director: "Alfred Plicploc"),
   Song(title: "De si bon matin", singer: "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érifiez 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 numberOfSong = 0
for media in library {
   if media is Song { // Ici on contrôle que le media est bien une chanson
      numberOfSong += 1
      }
   }
print("La librairie contient \(numberOfSong) chansons.")

Dans la boucle for, je ne sais pas si la variable media est du type Song ou du type Movie . La déclaration if media is Song , 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édez à 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 library {
   if media is Song {
      print(media.singer) // 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 Song . 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 library {
   if let song = media as? Song {
      print(song.singer)
   }
}

Ici nous venons de downcaster le type de la variable média.

Ah ? Et c’est grave docteur ? 🐰

Non, rassurez-vous, c’est assez courant. Caster est le principe qui consiste à forcer le typage d’une variable. Le as nous permet de le faire en Swift. Il arrive que dans l’héritage nous ayons besoin de caster le type de notre variable. Le but ici est de pouvoir accéder à des variables plus spécifiques de notre classe fille.

Si j’avais voulu, à l'inverse, forcer le type de ma chanson pour pouvoir m’en servir comme d’un média, j’aurais upcasté le type de ma variable.

Voyons ce qu'il se passe en détail :

  • si la variable media est du type Song :

    • une nouvelle variable est créée : la variable song de type Song , et qui contient les mêmes données que la variable media ;

    • on effectue les instructions à l'intérieur des accolades. La variable song y est disponible.

  • si la variable media n'est pas du type Song :

    • 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 le as si on est absolument certain du type de notre variable. Par exemple :

(library[1] as! Song).singer

Entre les parenthèses, on force l'évaluation de library[1] à Song , car on sait que c'est une chanson. Toute l'expression entre parenthèses est donc considérée comme une variable de type Song dont on peut obtenir, du coup, la propriété singer .

// Vérification que l'optionnel n'est pas nul
if optional != nil {
   // Faire quelque chose
}
// Déclaration optionnelle
if let unwrapped = optional {
   print(unwrapped)
}
// Déballage forcé
let unwrapped = optional!

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 !

Si vous ne savez pas le type d'un objet, utilisez as avec un point d'exclamation. Si vous êtes certain et voulez vérifiez le type, utilisez is. Si vous êtes certain et devez accéder à la variable, utilisez as avec un point d'interrogation.
Suivez le guide !

Utilisez 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

OK, mais à quoi ça sert ?

Il y a deux usages principaux.

Créez des 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 mixArray: [Any] = [12, "Blob", true, Bus()]

Dans ce tableau, je peux utiliser des valeurs de différents types grâce au type Any .

Vérifiez le type

Le deuxième cas courant d'utilisation, c'est quand on ne connaît 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 : ["error": "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] .

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 courants de la vérification des types.

À vous de jouer !

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 classe Bus , et la propriété children de la classe HomeRoadSection ;

  • 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.

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 et AnyObject .

  • Pour vérifier un type, on a trois méthodes : is , as? et as! .

  • Les types Any et AnyObject permettent aux variables d'accepter des valeurs de n'importe quel type (uniquement les instances de classe pour le type AnyObject ). 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.

Bravo, vous avez fini la partie sur l’héritage et vous voyez, tout s’est bien passé ! Un petit quiz pour vérifier si ces nouvelles notions sont bien comprises, et on se retrouve pour la dernière partie du cours, qui va vous permettre d'approfondir et de consolider vos connaissances sur la POO.

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