UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Polymorphisme et conversion de type

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.

Conversion de type avec typecasting

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.

Conversion de types communs avec des initialiseurs

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.

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.