Type Checking and Type Casting - OOP REVISITED - Understanding Swift Programming: Swift 2 (2015)

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