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.