Back to blog

Classes in Swift Explained


Aasif Khan
By Aasif Khan | Last Updated on June 9th, 2022 8:39 am | 5-min read

Classes are one of the most fundamental building blocks of Swift code. In this tutorial, we’re going to discuss how classes work, what instances are, how inheritance works, and much more.

Here’s what we’ll get into:

  • What’s a class and what do you use it for?
  • Working with properties and functions in a class
  • Classes vs. structs in Swift
  • Why it’s smart to organize and reuse your code
  • How subclassing and inheritance works
  • Overriding functions with override and super

What’s a Class in Swift?

Classes are the building blocks of your app. You use them to structure and organize your code. You typically use classes for complex data, and structs for simple ones.

Think about it like this:

  • Writing all your code in one big file works like a waterfall. The code is executed from top-to-bottom, and there’s not much structure.
  • Writing your code in classes works like building a house. You separate your code in classes, and reuse them for rooms, windows, doors, etcetera.

Let’s say we’re building an app you can use to draw something. One of the classes we’re including in the app is Circle. Like this:

class Circle {
var radius: Double = 0.0
var color: String = “”
}

This class Circle has 2 properties:

  1. A property radius of type Double and a default value of 0.0
  2. A property color of type String and a default value “”

A property is a variable or constant that belongs to a class. In the above code, we’ve added 2 properties: radius and color. You could say that every circle now has a radius and color.

You can imagine that this Circle class can be used to draw a circle of a given radius and color on screen, in a drawing app. In fact, we can also add a function to Circle to calculate its circumference. Like this:

class Circle {

func circumference() -> Double {
return 2.0 * .pi * radius
}
}

A function is a self-contained block of code that performs an operation, like calculating the circumference of a circle. Functions can also return a value to whoever called the function. You can take the value that the circumference() function returns, and pass that value around in your code.

See how we’re organizing the code that belongs to a circle, in the Circle class? That’s what classes are all about. You can organize components in your code, ranging from Invoice to Tweet to DatabaseController, and that helps you to create code that’s easy to reuse, maintain and extend.

You typically use classes for complex data types, like Timer or DateFormatter. Classes have a counterpart, called structures, that you typically use for simpler data types, like String or View.

Class, Instance and Object

Working with classes in Swift has 2 important aspects: the class and the instance. So far, we’ve created a class called Circle. Like this:

class Circle {
var radius: Double = 0.0

func circumference() -> Double {
return 2.0 * .pi * radius
}
}

Instances of a class are often simply called “object”. You got the class and the object, and the object is an instance of the class. Jargon like that often gets in the way of communication, but when learning about coding, it’s often smart to know the appropriate terminology.

If you want to use this class in your code, you’ll have to create an instance of the class. It’s like making a copy of it, but different. Check this out:

let sun = Circle()
sun.radius = 696340.0 // in km
print(sun.circumference())
// Output: 4375233.25

In the above code, we’ve created an instance of the Circle class by using the Circle() initializer. This instance is assigned to a constant called sun.

On the second line of the above code, we’re assigning a number to the radius property of the sun instance. And on the third line, we’re printing out the output of the circumference() function.

What’s the difference between the class and an instance? Let’s take a look at a metaphor:

  • A class is like the blueprint of a house. An architect draws the blueprint, and provides it to a foreman or construction worker who then builds the house.
  • An instance is like the house itself. It’s not a copy of the blueprint, but instead it is the blueprint brought to life; a construction based on the blueprint.

As with any blueprints, you can build an unlimited number of houses based on it. This is no different with classes and instances, and that’s the power of classes: you define them once, and reuse them everywhere.

Check this out:

let radii = [3.0, 4.0, 5.0, 10.0, 99.0, 42.0]

for radius in radii {
let circle = Circle()
circle.radius = radius
print(circle.circumference())
}

In the above code, we’re reusing the Circle class to calculate the circumference of an array with arbitrary radii. This is exactly what programming is about! How can you be as lazy as possible by creating and reusing code?

The concept of classes belongs to an overarching concept called Object-Oriented Programming (OOP). Within this category, you can learn more about structs, functions, properties, inheritance, protocols, and much more.

Subclassing and Inheritance

Classes have an essential characteristic, that structs don’t have. A class can subclass another class, and inherit the properties and functions of that class. Subclassing allows you to reuse components in your code, while customizing their functions by overriding them.

Take a look at the following class:

class Tweet {
var text: String = “”

func show() {
print(text)
}
}

In the above code, we’ve defined a class called Tweet. It has one property text of type String, and a function show() that prints out the value of text.

We’re now going to create a subclass of Tweet. Like this:

class MediaTweet: Tweet {
var image: String = “”
}

You can see that MediaTweet is a subclass of Tweet because its syntax is class subclass : superclass. The MediaTweet has now inherited all properties and functions of the Tweet class, and has also added a property of its own: image of type String.

Here’s how you’d use the MediaTweet class:

let tweet = MediaTweet()
tweet.text = “My hovercraft is full of eels”
tweet.image = “https://picsum.photos/300/300”
tweet.show()
// Output: My hovercraft is full of eels

What if we want to adjust the output of the show() function with our own implementation? You can do that by overriding the show() function like this:

class MediaTweet: Tweet {

override func show() {
print(“\(text) — \(image)”)
}
}

When we run the tweet.show() function again, its output is now:

My hovercraft is full of eels — https://picsum.photos/300/300
That’s because we’ve “redefined” the show() function. You can still access the superclass’ function within the show() function with super.show(). You’ll see this often in practical iOS development, where you still need to run the original of the overriden function.

Before we call it quits, let’s discuss one important principle in working with inheritance. When you subclass a class, you can use that subclass in places where the superclass is required. Here, check this out:

let tweet = MediaTweet()
tweet.text =

func upload(tweet: Tweet) {
print(“Uploading tweet…”)
tweet.show()
}

upload(tweet: tweet)

// Output: Uploading tweet…
// My hovercraft is full of eels — https://picsum.photos/300/300

In the above code, we’ve created an instance of MediaTweet and assigned it to the tweet constant. Just as before, we’re changing the text and image properties to a meaningful value.

In the second part of the code, we’re creating a function upload(tweet:). This function accepts one parameter of type Tweet. Inside the function, the data of the tweet is “uploaded” and printed out.

Finally, on the last line, we’re calling the upload(tweet:) function with an argument tweet, i.e. the function is called and the MediaTweet object is provided as input.

What’s so special about this? Take a look at the signature of the upload(tweet:) function. It accepts a parameter of type Tweet, but we’re providing it with another type: MediaTweet. The types don’t seem to match! Why is this valid code?

It’s because MediaTweet is a Tweet. The MediaTweet class inherits everything from Tweet, so we can be logically certain that MediaTweet is a Tweet – and more!

We can even test this hierarchy with the “is” keyword. Like this:

if tweet is Tweet {
print(“tweet is of class Tweet”)
}

if tweet is MediaTweet {
print(“tweet is of class MediaTweet”)
}

// Output: tweet is of class Tweet
// tweet is of class MediaTweet

Why is this an important principle to understand? In practical iOS development, you’ll come across many scenarios where you can provide a subclass of a class as a value, whose required type is the superclass you’re subclassing.

Differently said, you may end up working with a function that asks for a Tweet. You’ve subclassed that Tweet class to give it more functionality, like MediaTweet. You can still provide an instance of MediaTweet to a function that asks for type Tweet, like upload(tweet:), because one class subclasses the other. MediaTweet is a Tweet!

This substitution of a subclass for a superclass is formally described as the Liskov substitution principle, which is part of the SOLID principles. It has crucial consequences in coding, for example, that you cannot change the return type of a function you’re overriding. You also cannot change properties in a subclass, but you can override their getter and setter.

Further Reading

Awesome! We’ve discussed classes in this tutorial. Here’s what you learned:

  • A class is a building block for code, and you use them to organize your code better
  • You create a class, and then can create instances of that class
  • You can subclass any class, and then inherit its properties and functions


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts