Swift Class Inheritance
Class inheritance is a fundamental concept in object-oriented programming that allows you to define a class that inherits properties and methods from another class. In Swift, inheritance enables you to build relationships between classes, creating a hierarchy where subclasses extend and specialize the functionality of their parent classes.
Introduction to Inheritance
Inheritance is one of the four pillars of object-oriented programming (along with encapsulation, polymorphism, and abstraction). It allows you to:
- Reuse code from existing classes
- Extend functionality without modifying the original code
- Create specialized versions of more general classes
- Establish "is-a" relationships between objects
In Swift, only classes can inherit from other classes. Structs and enums do not support inheritance.
Basic Inheritance Syntax
To create a subclass in Swift, you specify the class name followed by a colon and the name of the parent class (also called the superclass):
class ParentClass {
// Properties and methods
}
class ChildClass: ParentClass {
// Additional properties and methods
}
Let's look at a concrete example:
class Vehicle {
var currentSpeed = 0.0
func description() -> String {
return "traveling at \(currentSpeed) miles per hour"
}
}
class Bicycle: Vehicle {
var hasBasket = false
}
In this example, Bicycle
inherits all properties and methods from Vehicle
. So a Bicycle
instance will have both the currentSpeed
property and the description()
method:
let bicycle = Bicycle()
bicycle.currentSpeed = 15.0
print("Bicycle is \(bicycle.description())")
// Output: Bicycle is traveling at 15.0 miles per hour
Adding Properties to Subclasses
Subclasses can add their own properties and methods in addition to those they inherit:
class Car: Vehicle {
var gear = 1
var brand: String
init(brand: String) {
self.brand = brand
}
func changeGear(to newGear: Int) {
gear = newGear
}
}
Now, Car
has all the properties and methods from Vehicle
, plus its own gear
property and changeGear()
method:
let myCar = Car(brand: "Toyota")
myCar.currentSpeed = 60.0 // Property inherited from Vehicle
myCar.changeGear(to: 3) // Method defined in Car
print("My \(myCar.brand) is \(myCar.description()) in gear \(myCar.gear)")
// Output: My Toyota is traveling at 60.0 miles per hour in gear 3
Overriding Methods and Properties
One of the most powerful aspects of inheritance is the ability to override methods and properties from the parent class. You use the override
keyword to indicate that you're intentionally providing a new implementation:
class Train: Vehicle {
override func description() -> String {
return "a train \(super.description())"
}
}
let train = Train()
train.currentSpeed = 100.0
print(train.description())
// Output: a train traveling at 100.0 miles per hour
Notice how we used super.description()
to call the parent class's implementation of the method. This allows us to extend the functionality rather than completely replace it.
Preventing Overrides
If you want to prevent a method or property from being overridden by subclasses, you can mark it as final
:
class Vehicle {
var currentSpeed = 0.0
final func maximumAllowedSpeed() -> Double {
return 120.0
}
}
class SportsCar: Vehicle {
// This would cause a compilation error:
// override func maximumAllowedSpeed() -> Double {
// return 200.0
// }
}
You can also mark an entire class as final
to prevent any class from inheriting from it:
final class UtilityVehicle {
// No class can inherit from UtilityVehicle
}
Initializers in Subclasses
When working with inheritance, initialization becomes more complex. Subclasses must ensure that all properties (both their own and inherited ones) are properly initialized.
Designated and Convenience Initializers
Swift has two types of initializers:
- Designated initializers: These are the primary initializers that initialize all properties introduced by the class.
- Convenience initializers: These are secondary, supporting initializers that call a designated initializer from the same class.
class Vehicle {
var currentSpeed: Double
var description: String
init(speed: Double, description: String) {
self.currentSpeed = speed
self.description = description
}
convenience init() {
self.init(speed: 0.0, description: "generic vehicle")
}
}
class Motorcycle: Vehicle {
var hasSidecar: Bool
init(speed: Double, description: String, hasSidecar: Bool) {
self.hasSidecar = hasSidecar
super.init(speed: speed, description: description)
}
convenience init(hasSidecar: Bool) {
self.init(speed: 0.0, description: "motorcycle", hasSidecar: hasSidecar)
}
}
Note that when creating an initializer in a subclass, you must:
- First initialize any properties specific to the subclass
- Then call
super.init()
to initialize the superclass's properties - Finally, you can modify any inherited properties if needed
Overriding Properties
You can override not only methods but also properties in subclasses:
class Vehicle {
var description: String {
return "A vehicle"
}
}
class Truck: Vehicle {
var cargoCapacity: Double
init(cargoCapacity: Double) {
self.cargoCapacity = cargoCapacity
}
override var description: String {
return "A truck with cargo capacity of \(cargoCapacity) tons"
}
}
let truck = Truck(cargoCapacity: 5.0)
print(truck.description)
// Output: A truck with cargo capacity of 5.0 tons
You can override both stored and computed properties, but with different rules:
- You can override a stored property with a computed property
- You can override property observers (willSet/didSet)
- You cannot override a stored property with another stored property
Inheritance and Polymorphism
Polymorphism is the ability to treat objects of different types through a common interface. Inheritance enables polymorphism in Swift:
class Vehicle {
func makeSound() -> String {
return "..."
}
}
class Car: Vehicle {
override func makeSound() -> String {
return "Vroom!"
}
}
class Bicycle: Vehicle {
override func makeSound() -> String {
return "Ring ring!"
}
}
// An array can contain objects of different types that share a common superclass
let vehicles: [Vehicle] = [Vehicle(), Car(), Bicycle()]
// Each object responds according to its actual type
for vehicle in vehicles {
print(vehicle.makeSound())
}
// Output:
// ...
// Vroom!
// Ring ring!
Real-world Example: Building a Media Library
Let's create a more comprehensive example of inheritance by building a simple media library system:
class MediaItem {
var name: String
var size: Int // size in MB
init(name: String, size: Int) {
self.name = name
self.size = size
}
func play() {
print("Playing media item: \(name)")
}
func displayInfo() -> String {
return "Media: \(name), Size: \(size)MB"
}
}
class Movie: MediaItem {
var director: String
var duration: Int // duration in minutes
init(name: String, size: Int, director: String, duration: Int) {
self.director = director
self.duration = duration
super.init(name: name, size: size)
}
override func play() {
print("Playing movie: \(name) directed by \(director)")
}
override func displayInfo() -> String {
return "Movie: \(name), Director: \(director), Duration: \(duration) min, Size: \(size)MB"
}
}
class Song: MediaItem {
var artist: String
var genre: String
init(name: String, size: Int, artist: String, genre: String) {
self.artist = artist
self.genre = genre
super.init(name: name, size: size)
}
override func play() {
print("Playing song: \(name) by \(artist)")
}
override func displayInfo() -> String {
return "Song: \(name), Artist: \(artist), Genre: \(genre), Size: \(size)MB"
}
}
// Using our media library
let mediaLibrary: [MediaItem] = [
Movie(name: "Inception", size: 4500, director: "Christopher Nolan", duration: 148),
Song(name: "Bohemian Rhapsody", size: 10, artist: "Queen", genre: "Rock"),
Movie(name: "The Social Network", size: 3800, director: "David Fincher", duration: 120)
]
// We can iterate through all items regardless of their specific type
for item in mediaLibrary {
// The appropriate version of these methods will be called based on the actual type
print(item.displayInfo())
item.play()
print("---")
}
Output:
Movie: Inception, Director: Christopher Nolan, Duration: 148 min, Size: 4500MB
Playing movie: Inception directed by Christopher Nolan
---
Song: Bohemian Rhapsody, Artist: Queen, Genre: Rock, Size: 10MB
Playing song: Bohemian Rhapsody by Queen
---
Movie: The Social Network, Director: David Fincher, Duration: 120 min, Size: 3800MB
Playing movie: The Social Network directed by David Fincher
---
This example demonstrates how inheritance allows you to create specialized types that still share a common interface, making your code more organized and flexible.
Type Checking and Casting
When working with inheritance hierarchies, you sometimes need to check an object's type or convert it from one type to another:
for item in mediaLibrary {
// Check if the item is a Movie
if item is Movie {
print("\(item.name) is a movie")
}
// Try to cast the item to a Song
if let song = item as? Song {
print("\(song.name) is a song in the \(song.genre) genre")
}
}
is
operator: Checks if an instance is of a certain subclass typeas?
operator: Attempts to downcast to a specific subclass type (returns an optional)as!
operator: Forcibly downcasts (use only when you're certain about the type)
Summary
Class inheritance in Swift provides a powerful mechanism for code reuse and specialization. In this guide, we explored:
- Basic inheritance syntax
- Adding properties and methods to subclasses
- Overriding methods and properties from parent classes
- Initializers in the context of inheritance
- Polymorphism and its relationship to inheritance
- Type checking and casting with inheritance hierarchies
Inheritance is a fundamental tool in object-oriented programming, but remember that Swift encourages composition over inheritance in many cases. When deciding whether to use inheritance, consider whether there's truly an "is-a" relationship between your classes.
Exercises
-
Create a
Shape
class with properties forarea
andperimeter
, then create subclasses forCircle
,Rectangle
, andTriangle
with appropriate implementations. -
Design a simple game with a
Character
base class and subclasses likeWarrior
,Mage
, andArcher
, each with different abilities and attributes. -
Extend the media library example to include an
Ebook
class with properties for author and number of pages.
Additional Resources
- Swift Documentation: Inheritance
- Apple Developer: Object-Oriented Programming with Swift
- Ray Wenderlich: Swift Inheritance Tutorial
Happy coding as you explore the power of inheritance in Swift!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)