Skip to main content

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):

swift
class ParentClass {
// Properties and methods
}

class ChildClass: ParentClass {
// Additional properties and methods
}

Let's look at a concrete example:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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.
swift
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:

  1. First initialize any properties specific to the subclass
  2. Then call super.init() to initialize the superclass's properties
  3. Finally, you can modify any inherited properties if needed

Overriding Properties

You can override not only methods but also properties in subclasses:

swift
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:

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:

swift
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:

swift
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 type
  • as? 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

  1. Create a Shape class with properties for area and perimeter, then create subclasses for Circle, Rectangle, and Triangle with appropriate implementations.

  2. Design a simple game with a Character base class and subclasses like Warrior, Mage, and Archer, each with different abilities and attributes.

  3. Extend the media library example to include an Ebook class with properties for author and number of pages.

Additional Resources

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! :)