Type checks and casts
Imagine an animal shelter. In its register, everyone is listed the same way — as an animal. To call someone to play with a ball, you first have to find out which of them is a dog.
In a program, such a register is an array of the parent class. In the lesson on inheritance, a constant of type Animal held a subclass instance. An [Animal] array can also hold both dogs and cats:
class Animal {
var name: String
init(name: String) {
self.name = name
}
}
class Dog: Animal {
func fetch() -> String { "\(name) brings the ball" }
}
class Cat: Animal {}
let shelter: [Animal] = [Dog(name: "Rex"), Cat(name: "Tom")]
print(shelter[0].name, shelter[1].name) // Rex TomLet's go through the program:
Dogadds the methodfetch()toAnimal, whileCatadds nothing — its braces are empty.shelteris an array of type[Animal], and it holds instances of two different subclasses.- Every
Animalhas the propertyname, soshelter[0].nameandshelter[1].nameare read without any checks.
But Animal has no fetch(). The line print(shelter[0].fetch()) does not compile, even though shelter[0] holds a dog:
error: value of type 'Animal' has no member 'fetch'The compiler knows only the type of the array — [Animal]. What it really holds becomes known while the program runs, and type checks and casts exist for that.
Type check: is
The is operator checks whether an instance belongs to the class you name, and answers true or false:
var dogs = 0
for pet in shelter {
if pet is Dog {
dogs += 1
}
}
print(dogs) // 1The loop walks through the shelter and counts the dogs. pet is Dog is true for Rex and false for Tom, so dogs is 1. An instance of a subclass of Dog would pass this check too: a puppy is a dog as well.
Type cast: as? and as!
Knowing that it is a dog is not enough: to call fetch(), you need a constant of type Dog. A type cast is an attempt to treat a value as a value of another type, for example an Animal as a Dog. This is not a conversion like Double(count): a cast creates no new value and works with the same instance. Swift has two operators for casting.
as?
as? tries the cast and returns an optional: an instance of the class you named, or nil if the class is different. You unwrap it with if let, like any optional:
for pet in shelter {
if let dog = pet as? Dog {
print(dog.fetch()) // Rex brings the ball
}
}For Rex the cast succeeds, and dog is a constant of type Dog with the method fetch(). For Tom the expression pet as? Dog gives nil, and the body of if is skipped.
as!
as! casts without a check and returns not an optional but a value of the target type directly. It resembles force unwrapping with !: if the class is wrong, the program crashes. shelter[0] as! Dog works because a dog is there, but a cat leads to an error — more on it below.
Here the shelter picture falls short. A cat can be listed as a dog in the register by mistake, but a type cast rewrites nothing. A Cat instance stays a cat, and as? only checks who it is.
Interviewers ask how as? differs from as!. The answer: as? returns an optional and gives nil on failure, while as! crashes the program on failure.
Common mistake
as! is used where the class is not guaranteed:
let tom = shelter[1] as! Dog // ✗The program crashes: shelter[1] holds a cat, and a cat cannot be cast to Dog. The message says that a value of type main.Cat cannot be cast to main.Dog. The program's name comes before the class name: after swiftc main.swift it is main. The numbers in parentheses next to the names differ on every run. If the class may turn out different, use as? with if let.
Key points
ischecks the class of an instance and answerstrueorfalse; a subclass instance passes the check too.as?casts and returns an optional: a value of the class you named, ornil.as!casts without a check and crashes the program if the class is wrong.- The compiler knows only the declared type: to call a subclass method on an element of
[Animal], cast the element withas?first.