Inheritance and Subclassing Explained in Swift

Written by LearnAppMaking on February 3 2021 in App Development, Swift

Inheritance and Subclassing Explained in Swift

With subclassing, a class can inherit functions and properties from another class. That allows you to reuse your code, which is a good thing. In this tutorial, we’ll discuss how you can use subclassing and inheritance in Swift.

Here’s what we’ll discuss:

  • What’s subclassing and inheritance, and why do you need it?
  • Overriding functions with override and super
  • A unicycle is a bike is a vehicle…
  • Why you can and cannot override properties
  • Alternatives to subclassing, like protocol extensions
  • When it’s smart to use inheritance (and when not)

Ready? Let’s go.

  1. What’s Inheritance in Swift?
  2. Overriding Functions and Properties
  3. When to Use Inheritance?
  4. Alternatives to Subclassing
  5. Further Reading

What’s Inheritance in Swift?

Inheritance in Swift means that one class inherits the functions and properties of another class. You do this by subclassing.

Just like you’ve inherited genes from your mother, and even grandfather, Swift classes can inherit things from a superclass.

Let’s take a look at an example:

class Vehicle
{
    var speed: Int = 0

    func describe() {
        print("moving at \(speed) km/h")
    }
}

In the above code, we’ve defined a class called Vehicle. It has one property speed of type Int with a default value of zero. The class also has a function describe(), which prints out some text.

Here’s how we can use that Vehicle class:

let rocket = Vehicle()
rocket.speed = 1080000000
rocket.describe()
// Output: moving at 1080000000 km/h

So far so good! It’s an instance of Vehicle, assigned to rocket, and we adjust the speed property and call the describe() function.

Let’s create a subclass next:

class Bike: Vehicle {
    var color: String = ""
}

In the above code, we’ve created a Bike class. It subclasses the Vehicle class. You can see that based on the class subclass: superclass { ··· syntax in the class definition.

There’s a hierarchy going on here:

  • Subclass: A subclass subclasses another class, and inherits all its properties and functions. That’s Bike.
  • Superclass: “Superclass” is the term for the class that you’re subclassing. Vehicle is the superclass of Bike.

The Bike class has now inherited all functions and properties of the Vehicle class. It’s also added a property of its own, color of type String. This color property is only available on instances of the Bike class.

Check this out:

let myBike = Bike()
myBike.speed = 15
myBike.color = "red"
myBike.describe()
// moving at 15 km/h

In the above code, we’ve created an instance of the Bike class. You can see that we’re setting the speed property to 15 km/h, for example. This speed property is inherited from Vehicle. The same goes for the describe() function. That’s what subclassing is all about!

You can only subclass one superclass, so one class cannot inherit from more than one superclass. However, you can subclass further “down”. Check this out:

class Unicycle: Bike {
    var electric = false
}

let fastUnicycle = Unicycle()
fastUnicycle.speed = 999999
fastUnicycle.color = "blue"
fastUnicycle.electric = true
fastUnicycle.describe()
// moving at 999999 km/h

In the above code, you’ve got a Unicycle class that subclasses Bike. Because Bike subclasses Vehicle, the Unicycle class now inherits everything from both classes.

A hierarchy has formed: VehicleBikeUnicycle. As such, we can say that a Unicycle is a Bike is a Vehicle. You can even test this relationship with the “is” keyword in Swift. Like this:

if fastUnicycle is Vehicle {
    print("fastUnicycle is a Vehicle")
}
// Output: fastUnicycle is a Vehicle

Awesome!

Overriding Functions and Properties

One of the main reasons for using inheritance, is that you want to reuse some of the existing code in a class, and replace some other parts of it. Instead of repeating code that already exists, you reuse some of it.

Overriding Functions

But what if you want to change what’s already there? That’s where overriding comes in. Check this out:

class Bike: Vehicle {
    var color: String = ""

    override func describe() {
        print("\(color) bike moving at \(speed) km/h")
    }
}

In the above code, we’ve overriden the describe() function. This effectively replaces the function with a new implementation.

You need to do 2 things to override a function:

  1. Repeat the function signature, including parameters and return type, exactly
  2. Prepend the function signature with the override keyword

Why do you need to repeat the function declaration? If you’re overriding a function, you can’t change the function name, its parameters or its return type. They need to stay exactly the same as the function you’re overriding.

If we run the same code we had before, you’ll see that the result is different:

let myBike = Bike()
myBike.speed = 15
myBike.color = "red"
myBike.describe()
// Output: red bike moving at 15 km/h

The new implementation of the describe() function now uses the color property of Bike, because we’ve overriden it. Neat!

What if you need to access the “previous” implementation of the function you’re overriding; the one from the superclass? You can access that function by using the super keyword. Here’s an example:

class Vehicle {
    ···
    func describe() -> String {
        return "moving at \(speed) km/h"
    }
}

class Bike: Vehicle {
    ···
    override func describe() -> String {
        return "\(color) bike " + super.describe()
    }
}

In the above code, the overridden describe() function in the Bike class prepends its return value with the color of the bike. As such, the output for any red bike is something like “red bike moving at 15 km/h”.

The way this works is with the super keyword. The code super.describe() will call the superclass implementation of the describe() function, as if you hadn’t overridden it. This only works from within the describe() function you’re overriding.

If you’re overriding a property’s getters and setters, you can access the property’s superclass value with super.property. More on that, below!

Why You Can’t Override Properties

Can you also override properties? Yes and no. Check this out:

class Vehicle 
{
    var speed: Int = 0
    ···
}

class Bike: Vehicle {
    override var speed: Double = 0.0
}

In the above code, we’re attempting to override the speed property in the Bike subclass, by changing its type from Int to Double. This won’t work! Why not?

First off, by overriding you can only provide a custom implementation. You can provide your own “contents” of the function or property, but you cannot change its definition. You can’t change function’s or property’s types and/or parameters.

There’s a second reason. Remember when we discussed that a Unicycle is a Bike is a Vehicle? As such, we can provide an instance of type Unicycle when a type of Vehicle is required, because they’re related. Check this out:

func duration(for vehicle: Vehicle, distance: Int) {
    let duration = distance / vehicle.speed
    print("it would take vehicle \(duration) hours to travel \(distance) km")
}

This function calculates how much time it would take for a vehicle to travel a given distance. See the function’s parameters? We’ve got one of type Vehicle, and another one for distance.

Here’s how you’d use it:

let myBike = Bike()
myBike.speed = 15

duration(for: myBike, distance: 30)
// Output: it would take vehicle 2 hours to travel 30 km

So far, everything works. The function duration(for:distance:) depends on the speed and distance values to be of type Int, because that’s what makes the distance / speed computation work OK. Both have the same type.

It’s also worth noting here that it’s OK to provide an instance of type Bike for a parameter whose type is Vehicle. That’s because Bike subclasses Vehicle, so we can logically assume that “a Bike is a Vehicle”.

What happens when you override the speed property, like we did before?

class Bike: Vehicle {
    override var speed: Double = 0.0
}

Suddenly, the computation distance / vehicle.speed changes types: Int divided by Double. That doesn’t work! The division operator / clearly states that it needs to divide 2 values of the same type.

It could have worked, of course, by converting from Int to Double. But that’s beyond the point! The key in overriding is that you cannot change the types in the code that depends on the property or function to work.

If you change a input or output type, and some other code down the line depends on that, you’re guaranteed to introduce bugs into the codebase. That’s why, by design, you cannot override properties or change a function’s parameters or return type.

Overriding Observers, Getters, Setters

What can you override, though? Well, because the principle states that you can override an implementation, you can change a property’s observers, getters and setters.

Check this out:

class Bike: Vehicle {
    override var speed: Int {
        didSet {
            print("changed speed from \(oldValue) to \(speed)")
        }
    }
    ···
}

let myBike = Bike()
myBike.speed = 15
// Output: changed speed from 0 to 15

In the above code, we’ve added a property observer called didSet to the speed property. It’s essentially a bit of code that’s executed when the value of the property speed changes. Inside didSet, you have access to the speed property itself, and to it’s previous value called oldValue.

Just as before, we’ve used the override keyword to indicate that we’re overriding the implementation of the property. You’ll also need to keep the name of the property, and it’s type, exactly the same.

You can override a superclass’s properties, and more specifically, the willSet and didSet property observers, as well as the get and set getters and setters. Just as with functions, you can use super to access the property’s value on the superclass. You can learn more about those in this tutorial: Properties in Swift Explained

When to Use Inheritance?

When should you use inheritance? We’ve already touched upon it a bit, and the main reason for using inheritance is code reuse.

Say you’re actually building an app that involves vehicles, cars, bikes, unicycles, motorcycles, and so on. Those things clearly have a relationship between them, so it makes sense to create a hierarchy. Something like this:

  • Vehicle
    • Car
      • RaceCar
      • FamilyCar
    • Bike
      • Bicycle
      • Motorcycle
      • Unicycle

You can imagine that a RaceCar and FamilyCar share characteristics that they’ve inherited from Car. And a Unicycle and a RaceCar have a few things in common, because they’re both vehicles.

However, there’s a probable chance that your next app will involve buttons, text fields or table view cells, and not cars and unicycles. When do you use inheritance and subclassing in real-world app development!?

A common use case is the view controller. In fact, every time you’re putting a view controller on screen, you’re subclassing the existing UIViewController class! That’s the only way it works.

With view controllers, you’re inheriting the functionality of a view controller. You also provide your own implementation for viewDidLoad(), and you add on multiple properties, functions, and so on.

The UIKit framework knows how to put a view controller on screen, and invokes your overridden viewDidLoad() function when necessary – which puts your User Interface on screen. Neat!

Alternatives to Subclassing

Subclassing has 3 important disadvantages:

  1. You also inherit functions and properties you don’t need
  2. It creates a rigid hierarchy between classes
  3. It’s unusable when types are related, but not descendants

(Also, you cannot subclass with structs.)

Take a modern framework like SwiftUI, for example. If you’re building User Interfaces (UIs) with SwiftUI, you don’t have to subclass anything. You’re composing views from pre-existing UI components, like VStack and Text.

SwiftUI does this in a remarkable way, which involves 3 alternatives to subclassing:

  1. Protocols. In SwiftUI, a View is a protocol. As such, it has properties, like body, that you need to provide. The SwiftUI framework then relies on the fact that all views that conform to the View protocol have a body property, and that’s the content it’ll put on screen.
  2. Extensions. Unlike subclassing, an extension will decorate an existing type with new functions. This allows you to augment existing types, and reuse them without subclassing. With a protocol extension you can make existing types conform to a protocol, which helps you create composable code.
  3. Generics. A generic is a flexible type or function. For example, a generic function can multiply integers and doubles without declaring the same function twice. This allows you to share/reuse functionality between a great number of types, without needing to subclass or repeat code. On top of that, opaque types make a concrete type depend on its implementation, which allows for even more flexible code.

In general, we can say that subclassing makes your code more rigid. In some cases, like with view controllers, a RoundedButton, or cars/unicycles, that’s exactly what you need. In other scenarios, composability and flexibility is what you need; it enables you to reuse code without creating rigid relationships between them. Awesome!

Further Reading

Inheritance is a logical answer to the question: “How can I be more lazy as a coder?” And in this case, laziness is good! When you reuse more code, you have to write less code, and that translates to easier to maintain, extend and fix. That’s not the whole story, of course. In this tutorial, we’ve discussed how, when and why it’s smart to use inheritance and subclassing in your Swift code.

Here’s what we got into:

  • What’s subclassing and inheritance, and why do you need it?
  • Overriding functions with override and super
  • A unicycle is a bike is a vehicle…
  • Why you can and cannot override properties
  • Alternatives to subclassing, like protocol extensions
  • When it’s smart to use inheritance (and when not)

Want to learn more? Check out these resources:

LearnAppMaking

LearnAppMaking

At LearnAppMaking.com, app developers learn how to build and launch awesome iOS apps.