Swift: Swift Inheritance and Polymorphism

Inheritance is one of the three pillars of object-oriented programming, allowing one class to build on another, reusing and extending existing behavior. This lesson dives into Swift's inheritance mechanism and the application of polymorphism.

1. What You'll Learn


2. A Real-World UI Developer Story

(1) Pain Point: Massive Code Duplication Across UI Components

Charlie is building a UI component library requiring Button, Label, TextField, and more — all with similar fundamental behavior:

SWIFT
class Button {
    var frame: CGRect
    var backgroundColor: UIColor
    func render() { /* Draw background */ }
    func handleTap() { /* Handle tap */ }
}
class Label {
    var frame: CGRect
    var backgroundColor: UIColor
    func render() { /* Draw background */ }
}

frame, backgroundColor, and render() are nearly identical in Button and Label. Every new component copies and pastes the same properties; changing a base behavior requires editing 10 files.

(2) Solution: Inheritance

SWIFT
class UIView {
    var frame: CGRect
    var backgroundColor: UIColor
    func render() { /* Draw base background */ }
}
class Button: UIView {
    override func render() {
        super.render()          // First, draw background
        // Then draw button-specific styling
    }
    func handleTap() { }
}
class Label: UIView {
    override func render() {
        super.render()
        // Then draw label-specific styling
    }
}

Common properties are defined once and reused by all subclasses. Changing base behavior requires editing only one file.

(3) Benefit: Reuse + Extensibility

Dimension Copy-Paste Style Inheritance
Code reuse 0% — each component independent 100% — base properties and methods
Change cost Edit 10 files Edit 1 base class
New component Copy entire file Inherit + add differences
Consistency Easy to miss edits Base class guarantees consistent behavior
Extensibility Override risk high override + super is controllable

3. Inheritance Basics

(1) Subclassing

100%
graph TB
    A[Animal] --> B["Properties: name, age"]
    A --> C["Method: makeSound()"]
    B --> D[Dog]
    C --> D
    D --> E["Property: breed"]
    D --> F["Override: makeSound()"]
    A --> G[Cat]
    G --> H["Property: color"]
    G --> I["Override: makeSound()"]
Concept Description
Base class / Superclass The class being inherited from; in Swift all classes ultimately derive from AnyObject
Subclass A class that inherits from a base class; can add new properties and methods
Single inheritance Swift only supports single inheritance (one subclass, one superclass)
Root class A class with no superclass is called a root class

▶ Example: Basic Inheritance

SWIFT
// ============================================
// Basic inheritance usage
// ============================================
// Base class
class Vehicle {
    var make: String
    var model: String
    var year: Int
    init(make: String, model: String, year: Int) {
        self.make = make
        self.model = model
        self.year = year
    }
    func description() -> String {
        return "\(year) \(make) \(model)"
    }
    func startEngine() {
        print("Engine started")
    }
}
// Subclass — inherits from Vehicle
class Car: Vehicle {
    var numberOfDoors: Int
    init(make: String, model: String, year: Int, numberOfDoors: Int) {
        self.numberOfDoors = numberOfDoors
        super.init(make: make, model: model, year: year)  // Call superclass initializer
    }
    func honk() {
        print("Beep beep!")
    }
}
// Subclass — inherits from Vehicle
class Motorcycle: Vehicle {
    var hasSidecar: Bool
    init(make: String, model: String, year: Int, hasSidecar: Bool = false) {
        self.hasSidecar = hasSidecar
        super.init(make: make, model: model, year: year)
    }
    func wheelie() {
        print("Doing a wheelie!")
    }
}
let car = Car(make: "Toyota", model: "Camry", year: 2022, numberOfDoors: 4)
print(car.description())  // Inherited method
car.honk()                // Car's own method

Output:

TEXT 📖 Display only
2022 Toyota Camry
Beep beep!

4. Overriding and super

(1) Overriding with override

A subclass can override a superclass method, computed property, or subscript using override:

Overridable override Required Notes
Instance methods Yes — must add Provides a new function body
Computed properties Yes — must add Provides new getter/setter
Subscripts Yes — must add Provides new subscript implementation
Stored properties No — cannot Stored properties cannot be overridden

▶ Example: Overriding Methods

SWIFT
// ============================================
// Overriding superclass methods with override
// ============================================
class Animal {
    var name: String
    init(name: String) { self.name = name }
    func makeSound() {
        print("\(name) makes a sound")
    }
}
class Dog: Animal {
    override func makeSound() {
        print("\(name) barks: Woof!")
    }
}
class Cat: Animal {
    override func makeSound() {
        print("\(name) meows: Meow!")
    }
}
let dog = Dog(name: "Buddy")
let cat = Cat(name: "Whiskers")
dog.makeSound()
cat.makeSound()

Output:

TEXT 📖 Display only
Buddy barks: Woof!
Whiskers meows: Meow!

(2) Calling the Superclass with super

Use super inside an override to call the superclass version:

SWIFT
// ============================================
// Calling superclass with the super keyword
// ============================================
class Shape {
    var color: String
    init(color: String) {
        self.color = color
        print("Shape init: \(color)")
    }
    func draw() {
        print("Drawing a shape in \(color)")
    }
}
class Circle: Shape {
    var radius: Double
    init(color: String, radius: Double) {
        self.radius = radius
        super.init(color: color)  // Complete own init first, then call super
    }
    override func draw() {
        super.draw()  // First, execute superclass drawing
        print("  It's a circle with radius \(radius)")
    }
}
let circle = Circle(color: "Red", radius: 5.0)
circle.draw()

Output:

TEXT 📖 Display only
Shape init: Red
Drawing a shape in Red
  It's a circle with radius 5

Tip: During initialization, a subclass must first complete assigning its own stored properties before calling super.init. This is Swift's two-phase initialization safety mechanism.


5. final and Polymorphism

(1) final Prevents Inheritance

The final keyword can prevent a class from being subclassed or a method/property from being overridden:

Usage Effect
final class This class cannot be subclassed
final func This method cannot be overridden
final var This property cannot be overridden

▶ Example: Using final

SWIFT
// ============================================
// final prevents inheritance and overriding
// ============================================
class Calculator {
    // Basic operations can be overridden
    func add(_ a: Int, _ b: Int) -> Int { return a + b }
    // Core logic must not be overridden
    final func process(_ a: Int, _ b: Int, operation: String) -> String {
        let result: Int
        switch operation {
        case "add": result = add(a, b)
        default: result = 0
        }
        return "Result: \(result)"
    }
}
// This subclass can override add, but not process
class AdvancedCalculator: Calculator {
    override func add(_ a: Int, _ b: Int) -> Int {
        print("Advanced add: \(a) + \(b)")
        return a + b
    }
    // override func process(...)  // Compile error!
}
let calc = AdvancedCalculator()
print(calc.process(3, 4, operation: "add"))

Output:

TEXT 📖 Display only
Advanced add: 3 + 4
Result: 7

(2) Polymorphism

Polymorphism allows a variable of a superclass type to refer to a subclass instance, dynamically dispatching to the subclass's overridden implementation:

100%
graph TB
    A["let shapes: [Shape] = [Circle(), Rectangle()]"] --> B["for shape in shapes"]
    B --> C["shape.draw()  ← polymorphism"]
    C --> D["Circle's draw()"]
    C --> E["Rectangle's draw()"]

▶ Example: Polymorphism in Practice

SWIFT
// ============================================
// Polymorphism — processing different types uniformly
// ============================================
class Employee {
    let name: String
    init(name: String) { self.name = name }
    func work() -> String {
        return "\(name) is working"
    }
    func bonus() -> Double {
        return 0
    }
}
class Developer: Employee {
    override func work() -> String { return "\(name) is writing code" }
    override func bonus() -> Double { return 5000 }
}
class Designer: Employee {
    override func work() -> String { return "\(name) is designing UI" }
    override func bonus() -> Double { return 3000 }
}
class Manager: Employee {
    override func work() -> String { return "\(name) is managing team" }
    override func bonus() -> Double { return 8000 }
}
// Polymorphism: process different subclasses uniformly
let team: [Employee] = [
    Developer(name: "Alice"),
    Designer(name: "Bob"),
    Manager(name: "Charlie")
]
var totalBonus = 0.0
for member in team {
    print(member.work())   // Dynamically calls each implementation
    totalBonus += member.bonus()
}
print("Total bonus: $\(totalBonus)")

Output:

TEXT 📖 Display only
Alice is writing code
Bob is designing UI
Charlie is managing team
Total bonus: $16000.0

6. Complete Example: UI Component Rendering System

SWIFT
// ============================================
// Complete example: UI component rendering system
// Features: Inheritance + overriding + super + final + polymorphism combined
// ============================================
import Foundation
// 1. Base class (subclassable)
class UIComponent {
    let id: String
    var x: Int
    var y: Int
    init(id: String, x: Int, y: Int) {
        self.id = id
        self.x = x
        self.y = y
    }
    func render() -> String {
        return "[\(id)] at (\(x), \(y))"
    }
    // Core logic — must not be overridden
    final func display() {
        print(render())
    }
}
// 2. Button subclass
class Button: UIComponent {
    let label: String
    init(id: String, x: Int, y: Int, label: String) {
        self.label = label
        super.init(id: id, x: x, y: y)
    }
    override func render() -> String {
        let base = super.render()
        return "\(base) [Button: \(label)]"
    }
}
// 3. TextField subclass
class TextField: UIComponent {
    var text: String
    init(id: String, x: Int, y: Int, text: String = "") {
        self.text = text
        super.init(id: id, x: x, y: y)
    }
    override func render() -> String {
        return "\(super.render()) [TextField: \"\(text)\"]"
    }
}
// 4. Polymorphic rendering
let components: [UIComponent] = [
    Button(id: "btn1", x: 10, y: 20, label: "Submit"),
    TextField(id: "txt1", x: 10, y: 60, text: "Hello"),
    Button(id: "btn2", x: 10, y: 100, label: "Cancel"),
    TextField(id: "txt2", x: 10, y: 140)
]
for component in components {
    component.display()  // Polymorphic call
}

Output:

TEXT 📖 Display only
[btn1] at (10, 20) [Button: Submit]
[txt1] at (10, 60) [TextField: "Hello"]
[btn2] at (10, 100) [Button: Cancel]
[txt2] at (10, 140) [TextField: ""]

❓ FAQ

Q Does Swift support multiple inheritance?
A No. Swift is a single-inheritance language (one subclass, one superclass). Multiple inheritance needs are fulfilled through protocols — a class can conform to multiple protocols.
Q Why must I sometimes call super when overriding a method?
A It's not mandatory, but if you don't call super, the superclass logic is completely replaced. The typical pattern is to call super first to execute common logic, then add subclass-specific behavior. Whether to call super depends on design intent.
Q Are final class and struct the same regarding inheritance prevention?
A Behaviorally yes — neither can be subclassed. But struct is a value type and class is a reference type. Use final class to prevent inheritance while keeping reference semantics. If you need neither inheritance nor reference semantics, use struct.
Q What's the difference between upcasting and downcasting?
A Upcasting treats a subclass object as a superclass type (automatic and implicit, e.g. Employee e = Developer(...)). Downcasting converts a superclass reference back to a subclass type (requires as? or as!), and the conversion may fail, so the result is optional.
Q Why must I initialize my own stored properties before calling super.init?
A This is Swift's two-phase initialization safety mechanism. Phase 1: initialize stored properties from subclass to superclass. Phase 2: customize from superclass to subclass. This ensures all stored properties have definite values before the superclass init can access them, preventing uninitialized state.

📖 Summary


📝 Exercises

  1. Basic: Create a Person base class (properties: name, age; method: introduce()), then create Student and Teacher subclasses that override introduce() and add their own properties (studentId, subject).
  2. Intermediate: Design a MediaItem base class with a title property and a play() method. Create Movie (extra: director) and Song (extra: artist) subclasses that override play(). Use a polymorphic array to play them all.
  3. Challenge: Implement a "Permission Control System": base class User with permissionLevel property and canAccess(_:) method. Create AdminUser, EditorUser, ViewerUser subclasses. AdminUser uses a final method override func canAccess(_:) that always returns true. Use a polymorphic array to check access for each user uniformly.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏