Understanding Swift Programming: Swift 2 (2015)
PART 4: OOP REVISITED
30. Type Checking and Type Casting
Swift provides capabilities for both determining the type of a value and for causing the system to temporarily interpret the type of a variable or constant as that of another type. These capabilities are known as type checking and type casting.
Why Type Checking and Type Casting are Needed
Suppose we have the following classes:
class Animal {
}
class Fish: Animal {
func printFish() {
print("I am a fish")
}
}
class Bird: Animal {
func printBird() {
print("I am a bird")
}
}
We can create an instance of each of the Fish and Bird classes:
var aBird = Bird()
var aFish = Fish()
And we can also create an array called twoAnimals and store both of these instances in the array:
var twoAnimals = [aBird, aFish]
What we have are two instances, one of the class Bird and the other of the class Fish.
Although arrays must contain values of the “same type”, Swift’s adherence to the subtyping (subtype polymorphism) principle, plus a very accommodating compiler, results in a rather liberal interpretation of this rule.
The instances of aBird and aFish are not the same type (Bird and Fish). But what the compiler will do is to tag the array twoAnimals with the type Animal. Types of Bird and Fish are allowed to be contained in an array of type Animal, because both are subclasses ofAnimal. (This is the subtyping principle.)
Now, suppose we want to look in detail at each of the instances. We might assign one of them to the variable obj:
var obj = twoAnimals[0]
It's important to recognize just what we are dealing with here. Although the array twoAnimals has a type of Animal, the actual objects within the array still have their original type. (This is the type of the class that created these as instances of that class.) When we do the assign to the variable obj, that variable will be inferred to be a type of Animal. But, internally, the object in obj will still have its original type of Bird.
Type Checking with is
We can test this with the type checking keyword is:
var obj = twoAnimals[0] // Get the bird instance
print(obj is Bird) // Prints: true
print(obj is Fish) // Prints: false
The type checking keyword will check the internal type associated with the instance, not the type associated with the variable, which is Animal.
When Internal Types and the Types of Variables Containing Them are Different
The variable obj contains an instance of the class Bird, and that class has a method called printBird. We want to execute that method. We could try the following:
obj.printBird
// Error: 'Animal' does not have a member named 'printBird()'
This gets an error. What's the problem? It is that the variable obj has a type of Animal. And the class Animal does not have a method named printBird. It's not enough for the internal representation of the instance to have the correct class name. The variable that it is contained in must also have the correct class name.
Type Casting with as, as? and as!
The solution to this is these of one of the so-called type casting operators, as, as? and as!
I say “so-called” because these operators do not change the type of a variable in the same way that they do in many other languages.
In Swift type casting is more tentative. It's as if the keyword as! means "Please accept this as if it was this particular type."
Here obj contains, as before, an instance of the Bird class. But we have, with the as! keyword, effectively (but temporarily) made the system accept obj as being of type Bird. This allows the printBird method to be executed.
Casting a variable with a type like Animal to be a type like Bird (a subclass of Animal) is known as downcasting. (See the section later on “What’s Up and What’s Down?”)
It is possible for downcasting to fail. If you try to downcast a variable type to a subclass that does not exist, the downcast will fail. The exclamation point in the as! keyword is intended to remind programmers of this fact. If you try to downcast to a class that does not exist, you will get a runtime error.
If there is any possibility that the subclass you want to downcast to might not exist, it is safer to use as? rather than as! This keyword will cause the operation to return a nil if the downcast fails, which you can then check for.
What's Up and What's Down?
In the bizarre world of computer science, a "tree" has only a single root and is upside-down, with the root at the top and branches pointing down and leaves at the bottom. Object oriented programming uses the tree metaphor as its basis for understanding classes and subclass relationships. A base class is thus at the top, with its subclasses being branches and eventually leaves below. Given this, a "downcast" means converting an instance of a given class to have (temporarily) have the type of one of its subclasses. (An “upcast”, the reverse of this, is possible in Swift but is not normally done.)
Hands-On Exercises
Go to the following web address with a Macintosh or Windows PC to do the Hands-On Exercises.
For Chapter 30 exercises, go to
understandingswiftprogramming.com/30