Swift Inheritance - Building Class Hierarchies
Welcome to Swift Inheritance! Inheritance is a fundamental concept in object-oriented programming that allows classes to inherit characteristics from other classes. In this guide, we’ll explore how to build class hierarchies, override behavior, and create powerful, reusable code.
What is Inheritance?
Inheritance allows a class to inherit properties, methods, and characteristics from another class. The class that inherits is called a subclass (or child class), and the class it inherits from is called a superclass (or parent class).
Key Points:
- ✅ Only classes support inheritance (not structs or enums)
- ✅ Subclasses inherit all properties and methods
- ✅ Subclasses can override inherited behavior
- ✅ Use
finalto prevent overriding - ✅ Use
superto access parent implementation
Why Use Inheritance?
- 🎯 Code reuse - Don’t repeat yourself
- 🎯 Logical hierarchies - Model real-world relationships
- 🎯 Polymorphism - Treat related objects uniformly
- 🎯 Extensibility - Build on existing functionality
Defining a Base Class
A base class (or superclass) doesn’t inherit from any other class.
Simple Base Class
class Vehicle { var currentSpeed = 0.0 var description: String { return "traveling at \(currentSpeed) miles per hour" }
func makeNoise() { // Default implementation (does nothing) }}
let someVehicle = Vehicle()print("Vehicle: \(someVehicle.description)")// Output: Vehicle: traveling at 0.0 miles per hourBase Class with Properties and Methods
class Animal { var name: String var age: Int
init(name: String, age: Int) { self.name = name self.age = age }
func makeSound() { print("\(name) makes a sound") }
func eat() { print("\(name) is eating") }
func sleep() { print("\(name) is sleeping") }}
let animal = Animal(name: "Generic Animal", age: 5)animal.makeSound() // Generic Animal makes a soundSubclassing
Create a subclass by placing the superclass name after the subclass name, separated by a colon.
Basic Subclass
class Bicycle: Vehicle { var hasBasket = false}
let bicycle = Bicycle()bicycle.hasBasket = truebicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")// Output: Bicycle: traveling at 15.0 miles per hourSubclass with Additional Properties
class Dog: Animal { var breed: String
init(name: String, age: Int, breed: String) { self.breed = breed super.init(name: name, age: age) }}
let dog = Dog(name: "Buddy", age: 3, breed: "Golden Retriever")dog.makeSound() // Buddy makes a sound (inherited)dog.eat() // Buddy is eating (inherited)Multiple Levels of Inheritance
class Vehicle { var currentSpeed = 0.0}
class Bicycle: Vehicle { var hasBasket = false}
class Tandem: Bicycle { var currentNumberOfPassengers = 0}
let tandem = Tandem()tandem.hasBasket = truetandem.currentNumberOfPassengers = 2tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")Overriding
Subclasses can provide their own implementation of inherited methods, properties, or subscripts.
Overriding Methods
class Animal { func makeSound() { print("Some generic sound") }}
class Dog: Animal { override func makeSound() { print("Woof! Woof!") }}
class Cat: Animal { override func makeSound() { print("Meow!") }}
let dog = Dog()dog.makeSound() // Woof! Woof!
let cat = Cat()cat.makeSound() // Meow!Overriding Properties
You can override inherited properties to provide custom getters and setters:
class Vehicle { var currentSpeed = 0.0 var description: String { return "traveling at \(currentSpeed) miles per hour" }}
class Car: Vehicle { var gear = 1
override var description: String { return super.description + " in gear \(gear)" }}
let car = Car()car.currentSpeed = 50.0car.gear = 3print(car.description)// Output: traveling at 50.0 miles per hour in gear 3Overriding Property Observers
You can add property observers to inherited properties:
class AutomaticCar: Car { override var currentSpeed: Double { didSet { gear = Int(currentSpeed / 10.0) + 1 } }}
let automatic = AutomaticCar()automatic.currentSpeed = 35.0print("AutomaticCar: \(automatic.description)")// Output: AutomaticCar: traveling at 35.0 miles per hour in gear 4Cannot Override Stored Properties Directly
class Vehicle { var wheels = 4}
// ❌ Error - cannot override stored property// class Bicycle: Vehicle {// override var wheels = 2// }
// ✅ Correct - set in initializerclass Bicycle: Vehicle { override init() { super.init() wheels = 2 }}The super Keyword
Use super to access the superclass version of methods, properties, or subscripts.
Calling Superclass Methods
class Vehicle { func describe() { print("This is a vehicle") }}
class Car: Vehicle { override func describe() { super.describe() // Call parent implementation print("Specifically, a car") }}
let car = Car()car.describe()// Output:// This is a vehicle// Specifically, a carAccessing Superclass Properties
class Shape { var name: String
init(name: String) { self.name = name }
func area() -> Double { return 0 }}
class Rectangle: Shape { var width: Double var height: Double
init(width: Double, height: Double) { self.width = width self.height = height super.init(name: "Rectangle") }
override func area() -> Double { return width * height }
func describe() { print("\(super.name) with area \(area())") }}
let rect = Rectangle(width: 10, height: 5)rect.describe()// Output: Rectangle with area 50.0Super in Initializers
class Person { var name: String
init(name: String) { self.name = name print("Person initialized: \(name)") }}
class Student: Person { var studentID: String
init(name: String, studentID: String) { self.studentID = studentID super.init(name: name) print("Student initialized with ID: \(studentID)") }}
let student = Student(name: "Alice", studentID: "S12345")// Output:// Person initialized: Alice// Student initialized with ID: S12345Preventing Overrides with final
Use final to prevent a class, method, or property from being overridden.
Final Methods
class Vehicle { final func turnOn() { print("Vehicle turned on") }
func drive() { print("Vehicle is driving") }}
class Car: Vehicle { // ❌ Error - cannot override final method // override func turnOn() { // print("Car turned on") // }
// ✅ OK - can override non-final method override func drive() { print("Car is driving") }}Final Properties
class Shape { final var numberOfSides: Int { return 0 }
var area: Double { return 0 }}
class Triangle: Shape { // ❌ Error - cannot override final property // override var numberOfSides: Int { // return 3 // }
// ✅ OK override var area: Double { return 10.0 }}Final Classes
Prevent an entire class from being subclassed:
final class Singleton { static let shared = Singleton() private init() {}
func doSomething() { print("Doing something") }}
// ❌ Error - cannot subclass final class// class ExtendedSingleton: Singleton {// }When to Use final
Use final when:
- ✅ You want to prevent modification of critical functionality
- ✅ Performance is critical (compiler optimizations)
- ✅ The design doesn’t allow for extension
- ✅ Security concerns (prevent unwanted subclassing)
final class SecurityManager { static let shared = SecurityManager() private init() {}
final func validateAccess() -> Bool { // Critical security check - must not be overridden return true }}Practical Examples
Example 1: Employee Hierarchy
class Employee { var name: String var id: String var baseSalary: Double
init(name: String, id: String, baseSalary: Double) { self.name = name self.id = id self.baseSalary = baseSalary }
func calculateSalary() -> Double { return baseSalary }
func workDay() { print("\(name) is working") }}
class Manager: Employee { var bonus: Double
init(name: String, id: String, baseSalary: Double, bonus: Double) { self.bonus = bonus super.init(name: name, id: id, baseSalary: baseSalary) }
override func calculateSalary() -> Double { return super.calculateSalary() + bonus }
func conductMeeting() { print("\(name) is conducting a meeting") }}
class Developer: Employee { var programmingLanguages: [String]
init(name: String, id: String, baseSalary: Double, languages: [String]) { self.programmingLanguages = languages super.init(name: name, id: id, baseSalary: baseSalary) }
override func workDay() { super.workDay() print("\(name) is coding in \(programmingLanguages.joined(separator: ", "))") }}
let manager = Manager(name: "Alice", id: "M001", baseSalary: 80000, bonus: 20000)print("\(manager.name)'s salary: $\(manager.calculateSalary())")// Output: Alice's salary: $100000.0
let dev = Developer(name: "Bob", id: "D001", baseSalary: 70000, languages: ["Swift", "Python"])dev.workDay()// Output:// Bob is working// Bob is coding in Swift, PythonExample 2: Game Characters
class Character { var name: String var health: Int var maxHealth: Int
init(name: String, health: Int) { self.name = name self.health = health self.maxHealth = health }
func takeDamage(_ amount: Int) { health = max(0, health - amount) print("\(name) took \(amount) damage. Health: \(health)/\(maxHealth)") }
func heal(_ amount: Int) { health = min(maxHealth, health + amount) print("\(name) healed \(amount). Health: \(health)/\(maxHealth)") }
func attack() -> Int { return 10 // Base damage }}
class Warrior: Character { var armor: Int
init(name: String, health: Int, armor: Int) { self.armor = armor super.init(name: name, health: health) }
override func takeDamage(_ amount: Int) { let reducedDamage = max(1, amount - armor) super.takeDamage(reducedDamage) }
override func attack() -> Int { return 20 // Warriors hit harder }
func shieldBlock() { print("\(name) blocked with shield!") }}
class Mage: Character { var mana: Int
init(name: String, health: Int, mana: Int) { self.mana = mana super.init(name: name, health: health) }
override func attack() -> Int { if mana >= 10 { mana -= 10 print("\(name) casts a spell!") return 30 // Spell damage } return super.attack() }
func restoreMana(_ amount: Int) { mana += amount print("\(name) restored \(amount) mana. Mana: \(mana)") }}
let warrior = Warrior(name: "Knight", health: 150, armor: 10)warrior.takeDamage(25) // Only 15 damage after armor
let mage = Mage(name: "Wizard", health: 100, mana: 50)print("Mage damage: \(mage.attack())") // Casts spellExample 3: Shape Hierarchy
class Shape { var color: String
init(color: String) { self.color = color }
func area() -> Double { fatalError("Subclass must override area()") }
func describe() { print("\(color) shape with area \(area())") }}
class Circle: Shape { var radius: Double
init(color: String, radius: Double) { self.radius = radius super.init(color: color) }
override func area() -> Double { return Double.pi * radius * radius }}
class Rectangle: Shape { var width: Double var height: Double
init(color: String, width: Double, height: Double) { self.width = width self.height = height super.init(color: color) }
override func area() -> Double { return width * height }}
class Square: Rectangle { init(color: String, side: Double) { super.init(color: color, width: side, height: side) }
override func describe() { print("\(color) square with side \(width) and area \(area())") }}
let circle = Circle(color: "Red", radius: 5)circle.describe() // Red shape with area 78.53981633974483
let square = Square(color: "Blue", side: 4)square.describe() // Blue square with side 4.0 and area 16.0Example 4: View Hierarchy (iOS-like)
class View { var frame: (x: Double, y: Double, width: Double, height: Double) var backgroundColor: String
init(x: Double, y: Double, width: Double, height: Double, color: String) { self.frame = (x, y, width, height) self.backgroundColor = color }
func draw() { print("Drawing view at (\(frame.x), \(frame.y))") }
func handleTap() { print("View tapped") }}
class Button: View { var title: String var action: (() -> Void)?
init(x: Double, y: Double, width: Double, height: Double, title: String) { self.title = title super.init(x: x, y: y, width: width, height: height, color: "Blue") }
override func draw() { super.draw() print("Drawing button with title: '\(title)'") }
override func handleTap() { print("Button '\(title)' tapped") action?() }}
class Label: View { var text: String
init(x: Double, y: Double, width: Double, height: Double, text: String) { self.text = text super.init(x: x, y: y, width: width, height: height, color: "Clear") }
override func draw() { super.draw() print("Drawing label with text: '\(text)'") }}
let button = Button(x: 100, y: 200, width: 150, height: 50, title: "Click Me")button.action = { print("Button action executed!")}button.draw()button.handleTap()Example 5: Vehicle Fleet
class Vehicle { var make: String var model: String var year: Int var mileage: Double = 0
init(make: String, model: String, year: Int) { self.make = make self.model = model self.year = year }
func drive(miles: Double) { mileage += miles print("\(make) \(model) drove \(miles) miles") }
func service() { print("Servicing \(make) \(model)") }}
class ElectricVehicle: Vehicle { var batteryLevel: Double = 100.0 var range: Double
init(make: String, model: String, year: Int, range: Double) { self.range = range super.init(make: make, model: model, year: year) }
override func drive(miles: Double) { let batteryUsed = (miles / range) * 100 batteryLevel = max(0, batteryLevel - batteryUsed) super.drive(miles: miles) print("Battery level: \(batteryLevel)%") }
func charge() { batteryLevel = 100 print("\(make) \(model) fully charged") }}
class Truck: Vehicle { var payloadCapacity: Double var currentLoad: Double = 0
init(make: String, model: String, year: Int, capacity: Double) { self.payloadCapacity = capacity super.init(make: make, model: model, year: year) }
func loadCargo(_ weight: Double) { if currentLoad + weight <= payloadCapacity { currentLoad += weight print("Loaded \(weight) lbs. Current load: \(currentLoad) lbs") } else { print("Cannot load - exceeds capacity!") } }}
let tesla = ElectricVehicle(make: "Tesla", model: "Model 3", year: 2023, range: 350)tesla.drive(miles: 100)tesla.charge()
let truck = Truck(make: "Ford", model: "F-150", year: 2023, capacity: 3000)truck.loadCargo(1500)truck.drive(miles: 50)Type Checking and Casting
Check types and cast instances in class hierarchies:
Type Checking with is
class Animal {}class Dog: Animal {}class Cat: Animal {}
let animals: [Animal] = [Dog(), Cat(), Dog(), Animal()]
var dogCount = 0for animal in animals { if animal is Dog { dogCount += 1 }}
print("There are \(dogCount) dogs") // 2Downcasting with as? and as!
for animal in animals { if let dog = animal as? Dog { print("Found a dog!") } else if let cat = animal as? Cat { print("Found a cat!") }}Best Practices
1. Favor Composition Over Inheritance
// ❌ Overusing inheritanceclass FlyingSwimmingAnimal: Animal { }
// ✅ Better - use protocolsprotocol Flying { func fly()}
protocol Swimming { func swim()}
class Duck: Animal, Flying, Swimming { func fly() { print("Flying") } func swim() { print("Swimming") }}2. Use final for Performance
// ✅ Compiler can optimizefinal class UtilityClass { static func helper() -> String { return "Helper" }}3. Call super When Overriding
class Base { func setup() { // Important setup }}
class Derived: Base { override func setup() { super.setup() // ✅ Don't forget! // Additional setup }}4. Keep Hierarchies Shallow
// ❌ Too deepclass A {}class B: A {}class C: B {}class D: C {}class E: D {}
// ✅ Better - shallow hierarchyclass Base {}class TypeA: Base {}class TypeB: Base {}Summary
Inheritance is a powerful feature for building class hierarchies:
Subclassing 🏗️
- Inherit properties and methods
- Add new functionality
- Only for classes
Overriding 🔄
- Customize inherited behavior
- Use
overridekeyword - Can override methods and properties
super Keyword ⬆️
- Access parent implementation
- Required in initializers
- Optional in methods
final Keyword 🔒
- Prevent overriding
- Can apply to methods, properties, or entire class
- Performance benefits
Next Steps
Next topic:
- Topic 17: Protocols
- Protocol syntax
- Protocol requirements
- Protocol inheritance
- Protocol composition
- Protocol extensions
Practice Exercises
- Create an
Animalhierarchy withDog,Cat, andBird - Build a
Documentclass hierarchy withTextDocumentandSpreadsheetDocument - Create a
UIControlhierarchy similar to iOS frameworks - Implement a payment processor hierarchy (
CreditCard,DebitCard,PayPal) - Build a game character hierarchy with different types
- Create a shape drawing system with various shapes
Master inheritance to build extensible, maintainable code! 🎯
Remember: Inheritance creates “is-a” relationships - use it wisely!