Swift Inheritance

Class inheritance lets one class build on another's properties and behavior, overriding what needs to change while reusing the rest through super.

Class Inheritance

A class can inherit from exactly one other class, becoming a subclass of that superclass. The subclass automatically gains all of the superclass's properties and methods, and can add new ones or override existing ones to change their behavior.

Overriding Methods and Properties

To change how an inherited method or computed property behaves, mark the new implementation with the override keyword. Swift checks at compile time that an overridden member really does exist on the superclass, which prevents typos from silently creating a brand-new method instead of replacing the intended one.

  • Only classes support inheritance in Swift; structs and enums cannot inherit.
  • The override keyword is required and is checked by the compiler against the superclass.
  • Call super.methodName() inside an override to reuse the superclass's implementation instead of replacing it entirely.
  • Mark a class or member final to forbid further overriding or subclassing.
  • A subclass's designated initializer must call a superclass designated initializer, typically via super.init, before it can use self.

Overriding a method

class Vehicle {
    let make: String
    var speed = 0.0

    init(make: String) {
        self.make = make
    }

    func describe() -> String {
        "\(make) traveling at \(speed) km/h"
    }
}

class Car: Vehicle {
    var numberOfDoors: Int

    init(make: String, numberOfDoors: Int) {
        self.numberOfDoors = numberOfDoors
        super.init(make: make)
    }

    override func describe() -> String {
        super.describe() + " with \(numberOfDoors) doors"
    }
}

let car = Car(make: "Toyota", numberOfDoors: 4)
car.speed = 80
print(car.describe())   // Toyota traveling at 80.0 km/h with 4 doors

Using super and a convenience initializer

class Animal {
    let name: String

    init(name: String) {
        self.name = name
    }

    convenience init() {
        self.init(name: "Unnamed Animal")
    }

    func makeSound() -> String {
        "..."
    }
}

class Dog: Animal {
    override func makeSound() -> String {
        "\(name) says Woof!"
    }
}

let generic = Animal()
let rex = Dog(name: "Rex")

print(generic.makeSound())   // ...
print(rex.makeSound())       // Rex says Woof!
Note: Mark a class final (final class Square) when you know it should never be subclassed. This also lets the compiler skip dynamic dispatch for its methods, which can make calls slightly faster.

Initializer Inheritance

Swift distinguishes designated initializers, which fully set up a class's own properties and then hand off to the superclass, from convenience initializers, which call another initializer in the same class. A required initializer forces every subclass to provide its own implementation, which is useful when a base class can't guess how subclasses should be constructed.

Designated, convenience, and required initializers

class Shape {
    var name: String

    required init(name: String) {
        self.name = name
    }

    func area() -> Double {
        0
    }
}

class Polygon: Shape {
    var sides: Int

    required init(name: String) {
        self.sides = 0
        super.init(name: name)
    }

    convenience init(name: String, sides: Int) {
        self.init(name: name)
        self.sides = sides
    }
}

final class Square: Polygon {
    var length: Double = 0

    convenience init(length: Double) {
        self.init(name: "Square", sides: 4)
        self.length = length
    }

    override func area() -> Double {
        length * length
    }
}

let square = Square(length: 5)
print("\(square.name): \(square.area())")   // Square: 25.0
Initializer TypePurposeRule
DesignatedFully initializes all properties introduced by the classMust call super.init on its superclass (except for root classes)
ConvenienceCalls another initializer in the same class to simplify setupMust call self.init, never super.init directly
RequiredForces every subclass to implement itMarked with the required keyword; subclasses must repeat required or provide it automatically
Note: In a subclass's designated initializer, you must call super.init before reading self or any inherited property. Calling self.method() or reading an inherited property before super.init is complete is a compile-time error.

Exercise: Swift Inheritance

How does a Swift class specify that it inherits from another class?