keepcalculator

06 Numbers and Powers

Swift Inheritance: A Practical Guide

Card No. 06·54816 · Updated · Standard formula, shown below

Swift inheritance lets a class (the subclass) take on the properties and methods of another class (the superclass), and then add or override its own behavior. You use it to reuse code, build on existing types, and create a clear hierarchy of related classes. This guide explains the syntax, rules, and practical considerations of inheritance in Swift.

What Is Inheritance in Swift?

Inheritance is a core object-oriented programming feature that Swift supports for classes only (not structs or enums). When a class inherits from another, it automatically gets all the non-private properties and methods of its superclass. The subclass can then add new stored properties, computed properties, and methods, or override existing ones to change behavior.

For example, a base class Vehicle might have a speed property and a move() method. A subclass Car can add a numberOfDoors property and override move() to include a honk. This promotes code reuse and establishes an “is a” relationship: a Car is a Vehicle.

How to Define Inheritance

You declare a subclass with a colon after the class name, followed by the superclass name. In Swift, a class can inherit from only one superclass (single inheritance), but it can conform to multiple protocols.

class Vehicle {
    var speed = 0
    func move() {
        print("Moving at \(speed) km/h")
    }
}

class Car: Vehicle {
    var numberOfDoors = 4
}

let myCar = Car()
myCar.speed = 120
myCar.move() // "Moving at 120 km/h"

Here, Car inherits speed and move() from Vehicle, and adds its own property. The subclass can be used wherever the superclass is expected (upcasting).

Overriding Methods and Properties

To change an inherited behavior, use the override keyword. This tells the compiler you intend to replace the superclass implementation. You can override instance methods, type methods, computed properties, and property observers (but not stored properties).

class Bicycle: Vehicle {
    override func move() {
        print("Pedaling at \(speed) km/h")
    }
}

When overriding a property, you must match the type and be at least as accessible as the original. You can also call the superclass version using super, e.g., super.move(), which is useful when you want to extend rather than replace behavior.

Initializer Inheritance and Rules

Swift has specific rules for initializers. By default, a subclass does not inherit its superclass initializers. However, if the subclass provides default values for all its new stored properties and doesn’t define any designated initializers, it automatically inherits all of the superclass’s designated initializers. If it overrides all of the superclass’s designated initializers, it inherits the convenience initializers.

When you write a designated initializer in a subclass, you must call a designated initializer from the immediate superclass before accessing any properties or methods of self. This ensures all stored properties are initialized before use.

class Car: Vehicle {
    var numberOfDoors: Int
    init(numberOfDoors: Int) {
        self.numberOfDoors = numberOfDoors
        super.init() // Vehicle's designated init
    }
}

Preventing Overrides with final

You can mark a class, method, or property as final to prevent it from being overridden. This is useful for enforcing design constraints or for performance optimizations. For example:

final class Engine {
    func start() { /* ... */ }
}
// class TurboEngine: Engine {} // Error: cannot inherit from final class

Marking a whole class as final means it cannot be subclassed at all. Marking a method as final means a subclass can inherit it but not override it.

Type Checking and Casting

Inheritance enables polymorphism, and Swift provides operators to check and cast types at runtime. Use is to check if an instance is of a certain type, and as? or as! to downcast to a subclass.

let vehicles: [Vehicle] = [Car(), Bicycle()]
for v in vehicles {
    if let car = v as? Car {
        print("Car with \(car.numberOfDoors) doors")
    } else if v is Bicycle {
        print("A bicycle")
    }
}

Downcasting is a common source of runtime errors if you force-cast with as! incorrectly. Prefer optional binding with as? to safely handle failures.

Inheritance vs. Protocols and Extensions

Inheritance is often compared to protocols and extensions, but they serve different purposes. Inheritance is for classes and provides a concrete base implementation. Protocols define a blueprint of requirements that any type (class, struct, enum) can conform to, and extensions let you add functionality to an existing type without subclassing.

A common rule of thumb: use inheritance when you have a clear “is a” relationship and shared implementation; use protocols when you need to share an interface across unrelated types. In practice, many Swift developers prefer composition (using protocols and generics) over deep inheritance hierarchies because it’s more flexible and avoids fragile base classes.

Common Pitfalls and Misconceptions

One frequent mistake is trying to override a stored property. You cannot override a stored property; you can only override computed properties or add observers to inherited stored properties. Another pitfall is forgetting to call super.init() in a designated initializer, which leads to a compile error.

Also, initializer inheritance can be surprising: if a subclass defines any designated initializer, it stops inheriting all superclass initializers. This is a deliberate safety feature to ensure all stored properties are set.

Frequently Asked Questions

Can structs inherit from other structs in Swift?

No. Only classes support inheritance. Structs can conform to protocols and use extensions, but they cannot inherit from another struct or class. This is a key difference between value types and reference types.

What is the difference between override and super?

override is a keyword that tells the compiler you are providing a new implementation of an inherited method or property. super is a reference to the superclass instance, used to call the superclass’s version of the method or initializer. You use super inside an overriding method to extend the original behavior.

Can a Swift class inherit from multiple classes?

No. Swift supports single inheritance only. A class can have only one superclass, but it can conform to multiple protocols. If you need behavior from multiple sources, consider using protocols with default implementations (via extensions) or composition.

Why do I get an error when I try to override a stored property?

Stored properties are not overridable because they directly allocate memory in the instance. You can only override computed properties, or add property observers to inherited stored properties using override in the subclass. To change the default value of a stored property, set it in your subclass’s initializer.

Is inheritance the same as subclassing?

Yes, the terms are used interchangeably. Subclassing is the act of creating a new class that inherits from an existing class. The original class is called the superclass, parent class, or base class.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →