Swift uses up and down casting to convert instances of one class type to its sub or superclass within the same hierarchy.
Note: The up and down casting can only be performed within the same hierarchies to sub or superclasses.
Upcasting is changing the type of an instance of a subclass into its superclass. We can think of it as moving up within the same hierarchy.
Let's consider the following illustration where we have different classes arranged in a hierarchy, where MediaItem
is the parent class while Movie
Book
and Song
are its child classes. We'll use the following example throughout while discussing up and down casting:
The code snippet below shows the respective class definitions, where class MediaItem
only contains the name of the media item, while its subclasses comprise other details such as names of directors, authors, or artists.
class MediaItem {var name: Stringinit(name: String) {self.name = name}}class Movie: MediaItem {var director: Stringinit(name: String, director: String) {self.director = directorsuper.init(name: name)}}class Song: MediaItem {var artist: Stringinit(name: String, artist: String) {self.artist = artistsuper.init(name: name)}}class Book: MediaItem {var author: Stringinit(name: String, author: String) {self.author = authorsuper.init(name: name)}}
We already know that Swift allows upcasting, which means subclasses can be declared or treated as their parent class during their lifetime. It is explained with the help of the following illustration:
In this example, we'll try to establish why upcasting is necessary for object-oriented paradigms and what its benefits are.
In the following code snippet, an array named library
has been initialized that contains two movies, two books, and three songs. The type checker of Swift can deduce that the objects stored in the library
have the same superclass, so it converts the type of library to MediaItem
.
let library = [Movie(name: "Casablanca", director: "Michael Curtiz"),Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),Movie(name: "Citizen Kane", director: "Orson Welles"),Song(name: "The One And Only", artist: "Chesney Hawkes"),Song(name: "Never Gonna Give You Up", artist: "Rick Astley"),Book(name: "Wuthering Heights", author: "Emily Bronte"),Book(name: "Becoming", author: "Michelle Obama")]
To process the library
array, no explicit typecasting is needed because the type of library
has been converted to MediaItem
by the compiler at runtime. We can check the data type of library
using the following method:
let t = type(of:library)print("The type of library is \(t)")
is
operatorTo check the actual data types of elements inside the library
array, we can use the is
operator. The is
operator returns True
if the item belongs to some class and False
otherwise.
var movieCount = 0var songCount = 0var bookCount = 0for item in library {if item is Movie {movieCount += 1} else if item is Song {songCount += 1}else if item is Book {bookCount += 1}}print("Media library contains \(movieCount) movies and \(songCount) songs and \(bookCount) Books.")// Prints "Media library contains 2 movies and 3 songs and 2 books"
Downcasting is used to reconvert the object of superclass back to their subclasses. We can think of it as moving down the hierarchy. Like upcasting down casting can only be done within the same hierarchies.
Although the default type of library
is MediaItem
, but all the elements do not belong to the same class. Therefore, when we reconvert each element into its respective subclass, it may result in an error as downcasting to a single class is not possible.
That is why we have to make sure that each element of library
should be converted into its respective subclass type. The concept is illustrated from the following hierarchy where the superclass is being downcasted to its subclasses:
as?
operatorTo convert each element of the library
into its respective time, we can use an operator named as?
. This operator is used for optional downcasting and can be used when it is unsure if the downcast will succeed.
In the following code snippet, the first line accesses an element from library
and changes it into an object of type Song
. If the conversion is successful, library[0]
will be saved in song
, otherwise song
will point to nil
.
As we know that the first element of library
is not a song. Therefore, the downcast will be failed. To downcast the library
array, conditional statements can be used along with optional downcasting as?
to convert each element of the array to its respective subclass type.
let song = library[0] as? Songif song != nil{print("Downcast is successful")} else {print("song points to nil, downcast is unsuccessful")}
as!
operatorFor the cases where we exactly know the types of subclasses, Swift has an as!
operator. This operator is used for forced downcasting, where it is known that downcasting will succeed. As we know that the data type of the second element of library
the array, that is, library[1]
is Song
so in the following code example, we use forced conversion to subclass type.
let song = library[1] as! Songprint("Song is \(song.name), artist is \(song.artist).")
Free Resources