Blog / December 7, 2024 / 4 mins read / By Mahi Garg

Inheritance in Swift

Inheritance is a fundamental concept in many programming languages, including Swift, allowing developers to reuse code efficiently and create hierarchical relationships between types. Swift handles inheritance differently for classes, protocols, and structs, each offering unique features and constraints. In this blog, we’ll explore how inheritance works for these types in Swift, complete with examples and illustrations.

1. Inheritance in Classes

In Swift, classes are reference types that support traditional object-oriented inheritance. A class can inherit properties, methods, and behavior from another class. This is known as a parent-child or superclass-subclass relationship.

Example: Class Inheritance
class Animal {
    var name: String

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

    func sound() {
        print("Animals make sounds.")
    }
}

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

let dog = Dog(name: "Buddy")
dog.sound() // Output: Buddy says: Woof!
Key Points:
  • Single Inheritance: Classes in Swift support single inheritance; a class can inherit from only one parent class.
  • Overriding: Subclasses can override methods or properties of the parent class.
  • Final Classes: A class can be marked final to prevent it from being subclassed.

2. Inheritance in Protocols

Protocols define a blueprint of methods, properties, and other requirements that a conforming type must implement. Swift allows protocol inheritance, enabling a protocol to adopt the requirements of another protocol.

Example: Protocol Inheritance
protocol Vehicle {
    var speed: Int { get set }
    func drive()
}

protocol ElectricVehicle: Vehicle {
    func chargeBattery()
}

struct Tesla: ElectricVehicle {
    var speed: Int

    func drive() {
        print("Driving at \(speed) mph.")
    }

    func chargeBattery() {
        print("Battery is charging.")
    }
}

let myTesla = Tesla(speed: 75)
myTesla.drive()         // Output: Driving at 75 mph.
myTesla.chargeBattery() // Output: Battery is charging.
Key Points:
  • Multiple Protocol Inheritance: A protocol can inherit from multiple protocols.
  • Flexibility: Any type (class, struct, or enum) can conform to protocols with inherited requirements.
  • No Stored Properties: Protocols cannot define stored properties but can require conforming types to have them.

3. Structs and the Absence of Inheritance

Unlike classes, structs in Swift are value types and do not support inheritance. This design decision ensures that structs remain lightweight and focus on encapsulating values. However, structs can conform to protocols, which can mimic some inheritance-like behavior.

Example: Struct Conforming to Protocols
protocol Drivable {
    func drive()
}

struct Car: Drivable {
    func drive() {
        print("Driving a car.")
    }
}

struct Bike: Drivable {
    func drive() {
        print("Riding a bike.")
    }
}

let car = Car()
car.drive() // Output: Driving a car.

let bike = Bike()
bike.drive() // Output: Riding a bike.
Key Points:
  • Structs are composition-focused rather than inheritance-focused.
  • Protocols and extensions can be used to extend the functionality of structs.
Why Structs Do Not Support Inheritance

Structs are value types in Swift, meaning they are copied when assigned or passed around. Inheritance is fundamentally tied to reference types (like classes) because it involves shared memory and object-oriented relationships.

Illustrating Inheritance in Swift

Here’s a visual representation of how inheritance works for classes and protocols, and how structs differ:

Class Inheritance Diagram
     Animal
       |
      Dog

Animal is the parent (superclass). Dog is the child (subclass) that inherits from Animal.

Protocol Inheritance Diagram
   Vehicle
      |
ElectricVehicle

Vehicle is the base protocol. ElectricVehicle inherits from Vehicle and adds additional requirements.

Struct with Protocol Conformance

   Drivable
     /   \
   Car   Bike

Both Car and Bike conform to the Drivable protocol, implementing its required functionality.

Comparison: Class vs Protocol vs Struct Inheritance

Feature Class Inheritance Protocol Inheritance Struct (No Inheritance)
Supported Types Classes only Classes, structs, enums Not supported
Purpose Hierarchical relationships Defining shared behavior Value encapsulation
Multiple Inheritance Not allowed Allowed Not applicable
Overrides Subclasses can override parent methods Not applicable Not applicable
Value/Reference Type Reference Applicable to both Value

When to Use What?

Use Class Inheritance When:
  • There is a clear hierarchical relationship between types.
  • You need to share common behavior or properties.
  • Subclasses need to override or customize parent behavior.
Use Protocol Inheritance When:
  • You need to define shared behavior across unrelated types.
  • Conforming types can be of any kind (class, struct, or enum).
  • Flexibility and loose coupling are priorities.
Use Structs When:
  • You want lightweight, value-type behavior.
  • Inheritance is not required, and functionality can be added via protocols or extensions.

Conclusion

Swift offers robust support for inheritance in classes and protocols, while emphasizing composition over inheritance for structs. Understanding the distinctions between these mechanisms allows you to make informed design decisions based on your application’s needs.

  • Classes: Perfect for hierarchical, object-oriented designs.
  • Protocols: Great for defining reusable behavior across diverse types.
  • Structs: Lightweight and efficient for encapsulating data and leveraging composition.

By combining these features, you can design flexible, reusable, and efficient Swift code.

Comments