Type Casting in Swift Explained

Aasif Khan
By Aasif Khan | Last Updated on January 17th, 2023 3:53 pm | 5-min read

You use type casting in Swift to treat an instance of a particular class as a subclass or superclass in the same class hierarchy. But… what does that even mean?

Type casting is a powerful programming concept, because it helps you to write flexible and reusable code. It’s crucial for iOS developers to master, because type casting is frequently used in practical iOS development.

In this app development tutorial you’ll learn how to use type casting in Swift. We’ll take a look at upcasting with as, downcasting with as? and as!, and type checking with is.

What is a Type?

First things first. The Swift programming language uses types, like classes and structs, to represent different kinds of data in your app’s code.

Some examples:

  • Int is used for integer values, or “whole numbers”
  • Double is used for decimal-point values, like 3.1415
  • String is used for text, like “Tinker Tailor Soldier Spy”
  • UIButton is used for a button user interface element

Types in a programming language are there for us, the developers. We use types to represent different kinds of data. The Swift compiler doesn’t care much about a type, beyond the data it represents! It simply reads our code and translates it to 1’s and 0’s a computer can understand.

The Swift programming language is strong-typed. That means that every variable needs a type, and once you’ve given a variable in your code a particular type, it cannot be changed.

That’s pretty rigid and often leads to verbose code, so the Swift programming language has features like type casting, type inference, protocols, Any and AnyObject, opaque types and generics to create concise, flexible and reusable code.

Here’s an example of a variable with an explicit type annotation:

let riddle:String = “Tinker Tailor Soldier Spy”

In the above code you define a constant named riddle of type String, and you assign it the string literal “Tinker Tailor Soldier Spy”. On the second line the value of riddle is printed out. The type of riddle is explicitly provided with a type annotation, i.e. riddle: String.

See that bit of text, Tinker Tailor Soldier Spy? That’s human-readable text for us developers, but for Swift it’s just another way to represent data. This distinction is important. We’ll discuss why in the next chapter.

Let’s move on!

What is Type Casting?

With type casting you treat an object as one of another type. The underlying object doesn’t change, merely the type you use to describe that object.

Let’s look at an example. We’ll describe three different classes. The first class House has one property windows of type Int and a simple initializer. Like this:

class House
var windows:Int = 0

init(windows:Int) {
self.windows = windows

The second class Villa subclasses House, thereby inheriting its windows property. It also adds a hasGarage property, of a boolean type, that indicates whether this villa has a garage for a car.

class Villa: House
var hasGarage:Bool = false

init(windows:Int, hasGarage:Bool)
self.hasGarage = hasGarage

super.init(windows: windows)

The subclass : superclass syntax, such as Villa: House, indicates that Villa subclasses House, and thereby inherits the properties and functions of House. It makes sense: a villa is a house, just like a dirtbike and a car are both vehicles, subsequently inheriting the property wheels and function drive().

The third class Castle also inherits House. It adds a property towers, and has a simple initializer.

class Castle: House
var towers:Int = 0

init(windows:Int, towers:Int)
self.towers = towers

super.init(windows: windows)

The class hierarchy is as follows now:

  • House
  • Villa
  • Castle

Both Villa and Castle subclass House. As a result, they both have a property windows in addition to their own properties hasGarage and towers. It’s essential to note here that the Castle class does not have a hasGarage property, and Villa does not have a towers property.

Back to type casting. With type casting you can treat an instance of a class as an instance of another superclass or subclass within its class hierarchy.

Looking back on the class hierarchy, we can do this:

  • We can downcast an instance of type House to Villa or Castle
  • We can upcast an instance of type Villa or Castle to House
  • We can’t cast an instance of type Castle to Villa, or vice-versa

A helpful way to look at this, is to remember that type casting works vertically and not horizontally. You can go up and down but not left and right in the class hierarchy.

Let’s look at an example:

let house:House = Castle(windows: 200, towers: 4)

In the above code, this happens:

  • You declare a constant named house of type House
  • You initialize it with an instance of Castle, and provide it with 200 windows and 4 towers

Note that the type annotation for house, and the object we assign to it, have different types!

This is called upcasting. Because House and Castle are in the same class hierarchy, you can assign an instance of Castle to a variable of type House. As explained earlier, it’s like saying: a castle is a house.

The upcast is implicit, so you don’t explicitly code it. You could though, like this:

let house:House = Castle(windows: 200, towers: 4) as House

What’s so interesting about the above code, is the type of house and it’s actual value. Let’s check the dynamic type of house, like this:

print(type(of: house))
// Outputs: Castle

You can use the type(of:) function to get the type of a value. And it prints out Castle! Wait a minute…

How can house be of type Castle when we clearly declared it with type House? And it gets more interesting. Check this out:

// Output: error: value of type ‘House’ has no member ‘towers’

What’s happening?

  • We try to print out the value of house.towers, which should be 4, because we initialized house with an instance of Castle.
  • The class Castle has a property towers, so we should be able to get its value with house.towers. Right?
  • No! The type of house is House, and that class doesn’t have a property towers.

Remember when we talked about data and its representation? About the difference between the actual data, and the way you treat it? The above examples explain that difference.

Internally, the value of house has data as represented by the Castle class. It has windows and towers! But we’re describing it with the House class, and as a result, we can’t access the towers property.

Type casting can get you out of that mess by letting you treat an instance of a class as an instance of another class, within its own class hierarchy.

Here’s how. First, let’s start again with that implicit cast:

let house:House = Castle(windows: 200, towers: 4)

Even though house has a value of type Castle, it’s described as type House, so we can’t get to house.towers. Yet…

Let’s downcast house to type Castle. Like this:

let castle:Castle = house as! Castle

You use the as! keyword to force-downcast one type to another. More on that in the next chapter.

You can now get to towers, like this:

// Output: 4

There we go! The value was the same all along, but now we merely described it using a different type, so we can get to the property towers.

Here, I’ll prove to you that we’ve been using the same object all along:

print(house === castle)
// Output: true

The above code tests identicality with the === operator. Both house and castle refer to the exact same object. The House and Castle classes are merely used to describe them. (Feel free to test this yourself in a playground!)

Can we cast between unrelated types? No, you can only downcast or upcast. You can’t cast from Castle to Villa, like this:

let villa = Villa(windows: 15, hasGarage: true)
let castle = Castle(windows: 150, towers: 8)

let result = villa as! Castle

This results in a fatal error: cast from ‘Villa’ to unrelated type ‘Castle’ always fails.

Pfew! Let’s quickly summarize…

  • With type casting you can treat an instance of a class as an instance of another superclass or subclass within its class hierarchy.
  • You can downcast and upcast between subclasses and superclasses in the same hierarchy, but not sideways between subclasses of the same superclass
  • A type describes data, and you can use type casting to treat that data as from a different type

How To Use “as”, “is”, “as!” and “as?”

You can use 4 different syntaxes for casting in Swift:

  • as for upcasting
  • is for type checking
  • as! for force downcasting
  • as? for optional downcasting

Upcasting with “as”

Upcasting is implicit in most cases, so you’ll almost never use the as keyword when coding your app. As explained in the previous chapter, upcasting is possible when a subclass “is a” superclass, like how a Villa is a House.

Type Checking with “is”

You use the type check operator is to check the type of an instance. The expression returns a value of type Bool, so it’s perfect to use in a conditional.

Like this:

let tintagel:Castle = Castle(windows: 300, towers: 1)

if tintagel is Castle {
print(“It’s a castle!”)
} else if tintagel is Villa {
print(“It’s a villa!”)

The above expression tintagel is Castle returns true, so the first conditional clause is executed.

The is operator returns true for any implicit upcast in the class hierarchy. When you use is to check for a superclass type, it also returns true. Like this:

let house:House = Castle(windows: 123, towers: 3)

print(house is House) // Output: true
print(house is Castle) // Output: true
print(house is Villa) // Output: false
In the example above, you see that:

  • house is a House (superclass)
  • house is a Castle (subclass)
  • house is not a Villa (unrelated subclass)

OK, let’s get down to downcasting! (Pun intended.)

Force Downcasting with “as!”

Downcasting is the opposite of upcasting.

  • When you upcast, you cast from a subclass to a superclass (upwards)
  • When you downcast, you cast from a superclass to a subclass (downwards)

An upcast is succeeds because a Villa is a House. That same rule doesn’t always apply to downcasting, because a House is not always a Villa. As a result, downcasting can fail.

You can use two kinds of downcasting:

  • Force downcasting with as!
  • Optional downcasting with as?

If you’re familiar with optionals, you’ll probably see where this is going.

  • An expression that uses as? will return an optional value. When the downcast fails, the expression returns nil. When it succeeds, it returns a value.
  • An expression that uses as! will return a non-optional value. When the downcast fails, your code crashes with a fatal error. When it succeeds, it returns a value.

You should only use force downcasting with as! if you’re sure that the downcast will succeed. When a downcast fails with as!, your app crashes. This is similar to force unwrapping an optional.

Let’s look at an example. We’re still using the House, Villa and Castle classes we defined earlier.

let house:House = Castle(windows: 200, towers: 4)
print(house.towers) // This doesn’t work, House does not have “towers”

let castle:Castle = house as! Castle
print(castle.towers) // This works, Castle does have “towers”

In the above code we’re creating an instance of class Castle and assign it to a variable house of type House. Even though the instance is of type Castle, we can’t access the property towers because the type of house is House.

In the second part, we’re force downcasting house to type Castle with the as! keyword. This results in a non-optional value of type Castle that’s assigned to the constant castle, and subsequently we print out the value of the towers property.

Again, it’s not the value that changes! We’re working with an instance of Castle throughout the code. What changes is the type we use to describe or treat that value.

Optional Downcasting with “as?”

It’s often more convenient to use optional downcasting with the as? keyword. Optional downcasting is exactly the same as force downcasting, except that the expression returns nil when the downcast fails.

Here’s an example:

let house:House = Castle(windows: 200, towers: 4)
print(house.windows) // Output: 200

let villa:Villa? = house as? Villa
print(villa?.hasGarage) // Output: nil

Here’s what happens:

  • First, we define a constant named house of type House and assign an instance of Castle to it.
  • Then, we print out the value of house.windows. This is OK, because Castle inherits the windows property from House.
  • Then, we try to downcast house to type Villa and assign the result to the constant villa. However, the downcast fails because the value of house is of type Castle, and Castle and Villa can’t be cast to each other.
  • Finally, the expression villa?.hasGarage is nil, because villa is nil. (It uses optional chaining.)

Note that the type of villa is Villa? – an optional – because as? results in an optional. Like any other optional, you’ll have to unwrap it.

Let’s look an example that’s more practical.

var houses = [
Castle(windows: 100, towers: 3),
Villa(windows: 20, hasGarage: false),
Castle(windows: 999, towers: 12),
House(windows: 3),
Castle(windows: 93, towers: 8),
Villa(windows: 42, hasGarage: true)

In the above example you’re creating an array with instances of Castle, House and Villa. Thanks to polymorphism, the type of houses is inferred as [House].

Why? Because every instance in the array either is a House, or has House as its superclass. Make no mistake though, there are instances of 3 different classes in this array!

Then, let’s write a simple for-in loop. Like this:

for house in houses
if let castle = house as? Castle {
print(“A castle with \(castle.windows) windows and \(castle.towers) towers”)
else if let villa = house as? Villa {
print(“A villa with \(villa.windows) windows and \(villa.hasGarage ? “a” : “no”) garage”)
else {
print(“Just a house with \(house.windows) windows”)

Here’s what happens. The code loops over every item house in the houses array. The code within the squiggly brackets { and } is executed for every item in houses.

Within the loop you use optional casting and optional binding to check the type of house, and subsequently invoke one of three conditional clauses.

  • When house is an instance of Castle, the first if clause executes
  • When house is an instance of Villa, the second if clause executes
  • When house is neither of those two, the else clause is executed

Let’s look at that first expression house as? Castle. Because it uses optional downcasting, the result of that expression is an optional. It has Castle? as its type. So when the downcast succeeds, it returns a value, and when it fails, it returns nil.

You can combine that with optional binding. As a result, a value is assigned to castle when the optional downcast succeeds. When it fails, the conditional is passed over, and the next else if clause is evaluated.

In the above code we’re also combining if, else if and else to control the flow of our code. We respond to cases where house is either Castle or Villa, and when neither of those are true, the else class is invoked. At this point, house is of type House, so we can access house.windows.

The expression villa.hasGarage ? “a” : “no” uses the ternary conditional operator to evaluate whether villa.hasGarage is true or false, and respond accordingly. When hasGarage is true, the expression returns the string “a”, and when it’s false, the string “no” is returns. As a result, the complete string reads either “… has a garage” or “… has no garage”.

Type Casting in Practical iOS Development

So far we’ve only looked at examples of type casting. What does type casting look like in an actual iOS app?

I ran a few searches for type casting through the codebase of an app I built recently, and I found no less than 167 cases of as?. Here’s what I used type casting for:

  • Responding to different types of view controllers within a tab bar controller, e.g. to take action on a view controller with a specific type
  • Downcasting between object models, e.g. when you get a PFObject that you want to cast to PFUser
  • A lot of casting from Any to a specific type, e.g. when receiving objects via JSON
  • Occasionally using toll-free bridging between Swift types and Objective-C types, to use String as NSString for example
  • Downcasting subclassed views, in a table view controller with a custom table view cell subclass for example

Type casting is a frequent occurence in practical iOS development. Let’s look at an example.

When you want to use a custom table view cell in a table view controller, you subclass UITableViewCell. As a result you inherit the properties of the table view cell, and gain the ability to customize them or use your own custom UI with a XIB.

Then, when you want to provide your cell to the table view, you implement the following function:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: “customCell”, for: indexPath)

// Customize cell contents here…

return cell

See how that function returns an instance of UITableViewCell? In fact, the type of cell is UITableViewCell. You’ve defined a property on your custom subclass, so how are you going to access that now?

With type casting, of course! Like this:

let cell = tableView.dequeueReusableCell(withIdentifier: “customCell”, for: indexPath) as? MyCustomTableViewCell

You’ve subclassed UITableViewCell, so the downcast is possible, because both your subclass and UITableViewCell are part of the same class hierarchy.

The downcast succeeds as long as dequeueReusableCell(…) returns an instance of your subclass. You’ll need to take in account that the downcast can fail, so you use as? to gracefully handle the error when that happens.


Try Type Casting Yourself!

Practice is the shortest path to mastery. In this tutorial we’ve written a lot of code, and I wanted to give you the opportunity to play around with type casting.

You’ll find a Swift Sandbox below, including the House, Villa and Castle classes we used earlier. See if you can re-create the example code from the previous chapters, to improve your understanding of type casting.

Happy coding!

class House
var windows:Int = 0

self.windows = windows

class Villa: House
var hasGarage:Bool = false

init(windows:Int, hasGarage:Bool)
self.hasGarage = hasGarage

super.init(windows: windows)

class Castle: House
var towers:Int = 0

init(windows:Int, towers:Int)
self.towers = towers

super.init(windows: windows)

let house:House = Castle(windows: 200, towers: 12)

if let castle = house as? Castle {
print(“A castle with \(castle.windows) windows and \(castle.towers) towers”)

Further Reading

Pfew! That was quite some code we wrote there. I hope you now understand how type casting works, and how you can treat one type as another in Swift.

Here’s what you’ve learned:

  • What a type is, and why it matters
  • What type casting is, and how it affects the type of a value in Swift
  • How you can check the type of a value with is
  • How you can upcast with as, and downcast with as? and as!
  • How type casting is used in practical iOS development

Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts