Created by Apple in 2014, Swift is a popular open-source language for building iOS applications that has garnered a strong community of developers and a wealth of third-party content.
Like almost every other programming language, Swift has its own rules and syntax. In this guide, we’ll take a look at type casting in Swift, a popular concept in modern object-oriented programming languages.
A type is, in essence, the primitive equivalent of a class that is used to denote the kind of data stored in a variable. As each class differs from the others, so do the data types, allowing developers to distinguish variables according to what kind of data they hold. Classifying data types prevents type mismatch, which is a common error at compile time.
Types are irrelevant to a compiler. If data stored in a variable is eligible for the operations carried out later in the flow, processes will run accordingly.
However, if a type mismatch causes a break in the flow, you’ll see a compilation error. Even if a type mismatch does not explicitly break the flow, it may go unnoticed during the compilation process, leading to unexpected results when running a program.
Swift is a strongly-typed programming language. Every variable in Swift has a type associated with it, and once a type has been assigned to a variable, it cannot store data of any other type.
Weakly-typed languages are not as strict in this regard. For example, in languages like PHP and C, you can alter data types to a certain extent to gain more flexibility in your code.
The constraints of using types force a language to lose a big chunk of its flexibility. Type casting provides a way to gain some flexibility back.
Not all class variables hold primitive data like integers and strings. Most of a modern application’s logic relies on custom objects that are made possible via classes. From now on, we’ll refer to primitive data types and classes by types.
Type casting is a programming paradigm used in most object-oriented languages that allows developers to treat an object of one type like that of another. While type casting may not make much sense at first, it helps to simplify many routines in modern applications.
Type casting does not change the object concerned. Instead, it changes the type used to describe the object. For example, you obviously can’t change the type of an object holding a string from string
to integer
because it wouldn’t make sense in real life to treat a sentence as a number.
For type casting to occur, the concerned object’s original type and the new type must be either subclasses or superclasses of one another. Let’s say a class called Vehicle
has two subclasses, Car
and Truck
. You can cast an object of type Car
to Vehicle
, and vice-versa, or Truck
to Vehicle
, and vice-versa.
Now, let’s say there was another class called Ship
, which had no relation to Truck
, Car
, or Vehicle
. You would not be able to cast its objects to any of the types given above.
Upcasting is used to generalize a series of subclasses of a class by using a variable of the class itself. Let’s consider the example using Car
, Truck
, and Vehicle
.
We mentioned earlier that Car
or Truck
could be type cast to Vehicle
. An object of a subclass is type cast into an object of its superclass. Think of it as moving up in the class hierarchy.
Upcasting lets you store any kind of Vehicle
in a reference of type Vehicle
: Car
, Truck
, etc. We already know that these are descendants of the same class, Vehicle
, and they are bound to have some similarities. Upcasting their objects to the Vehicle
class uses this generalization, letting us run operations using loops and other routine paradigms.
Here’s an example of upcasting:
let truck:Truck = Vehicle(wheels:8)
In the code block above, you declare a reference named truck
, which is of type Truck
. Next, you initialize it with an instance of Vehicle
, which has eight wheels. You can see that the type annotation for the reference variable and the object assigned to it are entirely different.
As mentioned earlier, we can perform this operation because Truck
and Vehicle
belong in the same class hierarchy. It is exactly like saying a truck is a vehicle, which is logically correct.
In the example above, the upcast was implicit. However, you can make it visible by running the following code:
let truck:Truck = Vehicle(wheels:8) as Truck
Later, we’ll cover implicit upcasting in greater detail.
Downcasting is the opposite of upcasting, and it refers to casting an object of a parent class type to an object of its children class. Downcasting is used to reconvert objects of a children class that were upcasted earlier to generalize.
Let’s say you own two cars and three trucks. If you store them in a shared array, the type inference will decide the type of the array as Vehicle
, a common parent to both types.
If you try to get an item from the array, you will get an object of type Vehicle
. To change it back to its original type, you will need to downcast it into a Truck
or a Vehicle
.
It is essential to understand that not all vehicles on the list will be cars, which may cause the downcast to fail in some cases. To solve this problem, we’ll use two types of downcasting operators, which we’ll cover later.
Notice that Car
and Truck
share a common superclass, however, you can’t cast an object of type Car
to Truck
, or vice-versa. They are neither subclasses nor superclasses of one another. Therefore, horizontal casting is disabled, and you’ll get an error if you try to cast a Car
into a Truck
.
To perform the operations described above, you’ll use the following operators:
as
The as
operator is used to upcast objects. However, in most cases, upcasting is done implicitly, so you will not use as
frequently.
To reiterate, here’s an example of upcasting Chair
to Furniture
:
let furniture:Furniture = Chair(legs: 4) as Furniture
as?
The as?
operator is used for optional downcasting and is one of the two downcasting operators available in Swift. Use as?
when you are unsure if an object can be downcasted successfully.
If you try to downcast an object from a different class hierarchy, your program returns a nil
value upon encountering an error. To verify if the downcast was successful, you can put a simple check for nil
.
You can use the as?
operator with our Vehicle
example:
let truckObject = vehiclesArray[0] as? Truck
You’re instructing the control to access the first object from the array, downcast it to a Truck
, and store the result in the truckObject
variable.
If the downcast was successful, you’ll find a Truck
instance in truckObject
. If it fails, truckObject
will point to nil
. You can then check for nil
by running:
if truckObject != nil { print("The reference points to some value!") } else { print("The reference points to nil, and so the downcast was unsuccessful") }
as!
The as!
operator is used for forced downcasting, and it returns an object only if the type cast operation is possible. If you attempt to downcast an object from a different class hierarchy, your program will encounter a fatal error.
Here’s how you can use the as!
operator with our Vehicle
example:
let carObject = vehiclesArray[1] as! Car
The line of code above is instructing the control to access the second object from the array, downcast it to a Truck
, and store the result in the truckObject
variable. If the downcast fails, the program crashes.
With these points in mind, you should use the as!
operator only when you are sure that the object you are downcasting belongs to the class hierarchy, and the operation will be completed successfully.
You can also use the as!
operator in situations where the code’s flow requires a break if the types mismatch, which indicates that there may be unexpected results in the intermediate calculations.
is
The is
operator is used to check the type of an instance. The result of a type check using the is
operator is of type Bool
, which indicates whether the type matched or not, as seen in the code block below:
let car: Car = Car(wheels: 4) if car is Car { print("It is a Car.") } else if car is Truck { print("It is a Truck.") }
The output is:
It is a Car.
You can also use the is
operator to check for any implicit upcast. Let’s consider an example:
let car: Car = Vehicle(wheels: 4) print(car is Car) print(car is Truck) print(car is Vehicle)
The output for the code above is:
true false true
The output indicates that the car reference is of type Car
and the implicitly upcasted type Vehicle
. Since horizontal casting is not possible, Car
can never be of the type Truck
.
In this article, you learned about types and type casting in Swift and covered the various operators used to type cast objects.
Type casting is a concept that makes object-oriented programming very powerful and flexible. With the ability to move up and down the class hierarchy through upcasting and downcasting, you can make use of generalization as needed.
The key to retaining all of the information is to keep practicing. I hope you enjoyed the article. Happy coding!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.