Swift Inheritance

Inheritance lets one class acquire the properties and methods of another class. The class that shares its features is the parent (or superclass). The class that receives them is the child (or subclass). Think of it like a family tree — children inherit traits from parents but can also develop their own unique characteristics.

Basic Inheritance

class Animal {
    var name: String

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

    func speak() {
        print("\(name) makes a sound.")
    }
}

class Dog: Animal {
    func fetch() {
        print("\(name) fetches the ball!")
    }
}

let dog = Dog(name: "Rex")
dog.speak()    // Output: Rex makes a sound.   (inherited)
dog.fetch()    // Output: Rex fetches the ball! (own method)

Diagram: Inheritance Tree

           +----------+
           |  Animal  |  ← Superclass
           | name     |
           | speak()  |
           +----------+
           /          \
    +-------+        +---------+
    |  Dog  |        |   Cat   |  ← Subclasses
    | fetch()|       | purr()  |
    +-------+        +---------+

Dog and Cat inherit name and speak() from Animal.

Overriding Methods

A subclass can replace (override) a superclass method with its own version. Use the override keyword.

class Cat: Animal {
    override func speak() {
        print("\(name) says: Meow!")
    }
}

class Dog: Animal {
    override func speak() {
        print("\(name) says: Woof!")
    }
}

let cat = Cat(name: "Whiskers")
let dog = Dog(name: "Rex")

cat.speak()   // Output: Whiskers says: Meow!
dog.speak()   // Output: Rex says: Woof!

Calling the Superclass with super

Inside an overriding method, call the superclass version using super.

class Animal {
    func speak() {
        print("Animal speaks.")
    }
}

class Dog: Animal {
    override func speak() {
        super.speak()                // Calls Animal's speak()
        print("Dog barks too!")
    }
}

Dog().speak()
// Output:
// Animal speaks.
// Dog barks too!

Overriding Initializers

class Vehicle {
    var speed: Int

    init(speed: Int) {
        self.speed = speed
    }
}

class ElectricCar: Vehicle {
    var batteryLevel: Int

    init(speed: Int, batteryLevel: Int) {
        self.batteryLevel = batteryLevel
        super.init(speed: speed)   // Must call superclass init
    }

    func status() {
        print("Speed: \(speed) km/h | Battery: \(batteryLevel)%")
    }
}

let tesla = ElectricCar(speed: 150, batteryLevel: 80)
tesla.status()
// Output: Speed: 150 km/h | Battery: 80%

Overriding Properties

You can override a stored property from the superclass as a computed property in the subclass.

class Shape {
    var description: String {
        return "A shape"
    }
}

class Square: Shape {
    var side: Double

    init(side: Double) {
        self.side = side
    }

    override var description: String {
        return "A square with side \(side)"
    }
}

let sq = Square(side: 4.0)
print(sq.description)   // Output: A square with side 4.0

Preventing Override with final

Mark a method or class with final to prevent subclasses from overriding it.

class PaymentProcessor {
    final func processPayment(amount: Double) {
        print("Processing \(amount)...")
    }
}

// class FakeProcessor: PaymentProcessor {
//     override func processPayment(amount: Double) { }
//     ↑ ERROR: Cannot override 'final' method
// }

final class SecureVault {
    // Nothing can subclass SecureVault
}

Polymorphism

Polymorphism means one type can take many forms. A variable of type Animal can actually hold a Dog or a Cat. Swift calls the correct overridden method at runtime.

let animals: [Animal] = [Dog(name: "Rex"), Cat(name: "Whiskers"), Dog(name: "Buddy")]

for animal in animals {
    animal.speak()
}
// Output:
// Rex says: Woof!
// Whiskers says: Meow!
// Buddy says: Woof!

Type Casting

Checking Type with is

let animal: Animal = Dog(name: "Rex")

if animal is Dog {
    print("It's a dog!")
}
// Output: It's a dog!

Downcasting with as? and as!

if let dog = animal as? Dog {
    dog.fetch()   // Output: Rex fetches the ball!
}

Use as? (safe) to attempt a downcast and receive nil on failure. Use as! only when you are certain the type is correct.

Diagram: Polymorphism at Runtime

[Animal array]
   |
   +→ Dog   → dog.speak()   → "Rex says: Woof!"
   +→ Cat   → cat.speak()   → "Whiskers says: Meow!"
   +→ Dog   → dog.speak()   → "Buddy says: Woof!"

Same method call, different behavior — decided at runtime.

Summary

Inheritance lets subclasses reuse and extend superclass functionality. Use override to replace methods, super to call the parent version, and final to lock down critical logic. Polymorphism lets collections of parent-typed variables run different code at runtime based on the actual subclass stored inside.

Leave a Comment

Your email address will not be published. Required fields are marked *