Inheritance is one of the fundamental concepts of object-oriented programming (OOP) languages. It allows us to create a new class from an existing class and reuse the existing code and functionality. In this article, we will learn about inheritance in Swift and how to use it to create subclasses, override methods and properties, and implement polymorphism.
What is Inheritance?
Inheritance is a relationship between two classes, where one class inherits the methods, properties, and other characteristics from another class.
- The class that inherits from another class is called a subclass and the class that is inherited from is called a superclass.
- For example, if we have a class called Animal and another class called Dog that inherits from Animal, then Dog is a subclass of Animal and Animal is a superclass of Dog.
- In Swift, we use the colon “:” to indicate that a class inherits from another class.
Below is a Swift program to implement inheritance:
Swift
class Animal
{
var species: String = ""
func speak()
{
print ( "An animal of species \(species) is making a sound." )
}
}
class Dog: Animal
{
var breed: String = ""
func wagTail()
{
print ( "The dog of breed \(breed) is wagging its tail." )
}
}
let myDog = Dog()
myDog.species = "Canine"
myDog.breed = "Golden Retriever"
myDog.speak()
myDog.wagTail()
|
Output:
An animal of species Canine is making a sound.
The dog of breed Golden Retriever is wagging its tail.
Explanation:
- In this code, we showcase the principles of inheritance in Swift. When a class inherits from a superclass, it gains access to the methods and properties of that superclass.
- This means that the Dog subclass, in our example, inherits the species property and the speak method from its Animal superclass.
- However, the power of inheritance extends beyond mere access. it enables us to extend and tailor the inherited functionalities. In the Dog subclass, we introduce a new property named breed and a method called wagTail.
- This illustrates one of the primary advantages of inheritance – the ability to build on existing code while adding specialized behaviors.
- By following this approach, we effectively prevent the need to duplicate code across multiple classes.
- This not only streamlines our code but also enhances its modularity and maintainability.
- It’s a prime example of how inheritance can help us create more efficient and adaptable software systems in Swift.
How to Override Methods and Properties?
Sometimes, we may want to modify or extend the behavior of a method or a property that is inherited from a superclass.
- For example, we may want to change how an animal makes noise or add more information to its description.
- To do this, we can override the method or property in the subclass and provide a new implementation.
- This is called method overriding or property overriding.
- In Swift, we use the override keyword to indicate that we are overriding a method or a property in a subclass.
Below is Swift program to override methods and properties:
Swift
class Animal
{
var name: String = ""
var description: String
{
return "I am an animal named \(name)"
}
func makeNoise()
{
print ( "I can make noise" )
}
}
class Dog: Animal
{
override var description: String
{
return "I am a dog named \(name)"
}
override func makeNoise()
{
print ( "I can bark" )
}
}
let dog = Dog()
dog.name = "Rex"
print (dog.description)
dog.makeNoise()
class Labrador: Dog
{
override var description: String
{
return "I am a labrador named \(name), \(super.description)"
}
}
let lab = Labrador()
lab.name = "Max"
print (lab.description)
lab.makeNoise()
|
Output:
I am a dog named Rex
I can bark
I am a labrador named Max, I am a dog named Max
I can bark
Explanation:
- The code defines a superclass Animal with properties and methods.
- A subclass Dog inherits from Animal and customizes the description and makeNoise() properties and methods.
- An instance of a Dog named Rex is created.
- The overridden description property of Dog displays “I am a dog named Rex.”
- The overridden makeNoise() method of Dog prints “I can bark.”
- Another subclass Labrador is defined, as inheriting from “Dog.”
- The description property of Labrador is overridden, including the original superclass description using super.
- An instance of Labrador named Max is created.
- The overridden description property of Labrador combines both class names, displaying “I am a labrador named Max, I am a dog named Max.”
- The makeNoise() method inherited from Dog still prints “I can bark.”
Types of Inheritance
Swift supports different types of inheritance, depending on the relationship between the classes involved. Some of the common types of inheritance are:
1. Single inheritance
A subclass inherits from only one superclass. This is the most common type of inheritance in Swift. For example:
class Vehicle
{
// properties and methods of Vehicle
}
class Car: Vehicle
{
// properties and methods of Car
}
Here, the Car class inherits from the Vehicle class, which means that a Car instance can access the properties and methods of the Vehicle class, as well as its properties and methods.
2. Multilevel inheritance
A subclass inherits from another subclass, which in turn inherits from a superclass. This creates a chain of inheritance from the top-level superclass to the bottom-level subclass. For example:
class Vehicle
{
// properties and methods of Vehicle
}
class Car: Vehicle
{
// properties and methods of Car
}
class ElectricCar: Car
{
// properties and methods of ElectricCar
}
Here, the ElectricCar class inherits from the Car class, which in turn inherits from the Vehicle class. This means that an ElectricCar instance can access the properties and methods of all three classes in the hierarchy.
3. Multiple inheritance
A subclass inherits from more than one superclass. This allows a subclass to combine the features and behaviors of multiple superclasses. However, Swift does not support multiple inheritance for classes directly. Instead, Swift uses protocols to achieve a similar effect. A protocol defines a set of requirements that a conforming type must implement, such as properties, methods or subscripts. A class can conform to multiple protocols by listing them after the colon: in its declaration. For example:
protocol Flyable
{
// requirements for flying
}
protocol Swimable
{
// requirements for swimming
}
class Duck: Animal, Flyable, Swimable
{
// properties and methods of Duck
}
Here, the Duck class inherits from the Animal class and also conforms to the Flyable and Swimable protocols. This means that a Duck instance can access the properties and methods of the Animal class, as well as the requirements of the two protocols.
Is-a Relationship
In Swift, inheritance is an is-a relationship. That is, we use inheritance only if there exists an is-a relationship between two classes. An is-a relationship means that a subclass is a specific type or kind of its superclass. For example:
- A car is a vehicle.
- An apple is a fruit.
- A cat is an animal.
Here, we can use inheritance to model these relationships by creating subclasses for each specific type or kind.
Swift
class Vehicle
{
var brand: String
var year: Int
init (brand: String , year: Int )
{
self .brand = brand
self .year = year
}
func startEngine()
{
print ( "The \(brand) vehicle's engine is starting." )
}
}
class Car: Vehicle
{
var model: String
init (brand: String , year: Int , model: String )
{
self .model = model
super . init (brand: brand, year: year)
}
func drive()
{
print ( "The \(year) \(brand) \(model) is driving." )
}
}
class Fruit
{
var name: String
init (name: String )
{
self .name = name
}
func describe()
{
print ( "This is a \(name)." )
}
}
class Apple: Fruit
{
var variety: String
init (name: String , variety: String )
{
self .variety = variety
super . init (name: name)
}
func taste()
{
print ( "This \(name) is a \(variety) apple, and it tastes delicious." )
}
}
class Animal
{
var species: String
init (species: String )
{
self .species = species
}
func makeSound()
{
print ( "The \(species) is making a sound." )
}
}
class Cat: Animal
{
var name: String
init (name: String )
{
self .name = name
super . init (species: "Cat" )
}
func meow()
{
print ( "\(name) the cat says 'Meow!'" )
}
}
let myCar = Car(brand: "Toyota" ,
year: 2023, model: "Camry" )
myCar.startEngine()
myCar.drive()
let myApple = Apple(name: "Apple" ,
variety: "Honeycrisp" )
myApple.describe()
myApple.taste()
let myCat = Cat(name: "Whiskers" )
myCat.makeSound()
myCat.meow()
|
Output:
The Toyota vehicle's engine is starting.
The 2023 Toyota Camry is driving.
This is a Apple.
This Apple is a Honeycrisp apple, and it tastes delicious.
The Cat is making a sound.
Whiskers the cat says 'Meow!'
Using inheritance to express an is-a relationship helps us to reuse code, avoid duplication and maintain consistency across related classes.
Method Overriding
A subclass can provide its custom implementation of an instance method, type method, instance property, type property or subscript that it would otherwise inherit from a superclass. This is known as overriding. To override a characteristic that would otherwise be inherited, we prefix our overriding definition with the override keyword.
Below is the Swift program to implement method overriding:
Swift
class Animal
{
func eat()
{
print ( "I can eat" )
}
}
class Dog: Animal
{
override func eat()
{
print ( "I eat dog food" )
}
}
let genericAnimal = Animal()
let myDog = Dog()
genericAnimal.eat()
myDog.eat()
|
Output
I can eat
I eat dog food
Explanation:
- The code overrides the eat() method in the Dog subclass, which is originally defined in the Animal superclass.
- When you call the eat() method using a Dog object, the version of the method in the Dog class is executed.
- This behavior occurs because method overriding allows the subclass to provide its implementation of the method, replacing the one from the superclass.
- It enables customization and specialization of method behavior in the subclass while maintaining a consistent interface with the superclass.
Overriding allows us to customize and modify the behavior of inherited methods, properties or subscripts according to the needs of the subclass.
super Keyword
In Swift, we can access methods, properties or subscripts of the superclass from a subclass using the super keyword. The super keyword refers to the superclass instance within the current subclass instance. This allows us to invoke the superclass’s implementation of a method or access its properties.
Below is the Swift program to implement super keyword:
Swift
class Animal
{
func eat()
{
print ( "I can eat" )
}
}
class Dog: Animal
{
override func eat()
{
super .eat()
print ( "I eat dog food" )
}
}
var labrador = Dog()
labrador.eat()
|
Output
I can eat
I eat dog food
Explanation:
- The code overrides the eat() method in the Dog subclass, which originally belonged to the Animal superclass.
- By using super.eat(), we explicitly call the eat() method of the Animal superclass within the Dog subclass.
- When you call the eat() method using a Dog object, both the overridden eat() method in the Dog class and the superclass eat() method in the Animal class are executed.
- This behavior occurs because the super keyword allows you to access and invoke the superclass’s implementation of a method.
- It provides a way to extend or build upon the functionality of the superclass method while still using it.
Using super enables us to access and utilize inherited characteristics of the superclass from within the subclass, allowing for the customization of behavior while preserving and extending the functionality of the parent class.
Property Overriding
A subclass can also override inherited properties of a superclass by providing its custom getter and setter for the properties or adding property observers to them. Property observers are code blocks that are executed when a property’s value is set or changed. There are two types of property observers: willSet and didSet. The willSet observer is called before the value of a property is stored and the didSet observer is called after the value of a property is stored.
To override a property, we use the same syntax as for overriding a method, but we also specify what aspect of the property we want to override: its getter, its setter or its observers.
Example 1: Below is the Swift program to implement property overriding:
Swift
class Vehicle
{
var currentSpeed = 0.0
}
class Car: Vehicle
{
override var currentSpeed: Double
{
get
{
return super .currentSpeed * 1.6
}
set
{
super .currentSpeed = newValue / 1.6
}
}
}
let myCar = Car()
myCar.currentSpeed = 60.0
let speedInKPH = myCar.currentSpeed
print ( "Current speed: \(speedInKPH) kph" )
|
Output
Current speed: 60.0 kph
Explanation:
- The code overrides the currentSpeed property in the Car subclass, originally defined in the Vehicle superclass.
- Custom getter and setter are provided in the Car subclass to convert between miles per hour and kilometers per hour when accessing or setting the property.
- The super keyword is used to access and modify the stored value of currentSpeed in the Vehicle superclass.
- This allows for specialized handling of the property within the subclass while still interacting with and modifying the underlying value in the superclass.
Example 2:
Swift
class Vehicle
{
var description: String
{
return "traveling at \(currentSpeed) miles per hour"
}
var currentSpeed = 0.0
}
class Car: Vehicle
{
override var description: String
{
return super .description + " in a car"
}
}
let myCar = Car()
myCar.currentSpeed = 60.0
let carDescription = myCar.description
print (carDescription)
|
Output
traveling at 60.0 miles per hour in a car
Explanation:
- In this code, the description property of the Car subclass is overridden, originally defined in the Vehicle superclass.
- A custom getter is provided for the description property within the Car subclass.
- The custom getter appends “in a car” to the value returned by super.description, which is the description property of the Vehicle superclass.
- This customization allows you to modify the behavior of the description property for the Car subclass while incorporating the description from the superclass.
Example 3:
Swift
class Vehicle
{
var currentSpeed = 0.0
{
didSet
{
print ( "The speed changed to \(currentSpeed)" )
}
}
}
class Car: Vehicle
{
override var currentSpeed: Double
{
willSet
{
print ( "The speed will change to \(newValue)" )
}
didSet
{
print ( "The speed changed by \(currentSpeed - oldValue)" )
}
}
}
let myCar = Car()
myCar.currentSpeed = 60.0
myCar.currentSpeed = 80.0
|
Output
The speed will change to 60.0
The speed changed to 60.0
The speed changed by 60.0
The speed will change to 80.0
The speed changed to 80.0
The speed changed by 20.0
Explanation:
- In this code, the currentSpeed property of the Car subclass is overridden, originally defined in the Vehicle superclass.
- Custom willSet and didSet property observers are added to monitor and respond to changes in the property’s value.
- In the willSet observer, you can access and work with the new value (newValue) before it’s set.
- In the didSet observer, both the new value (newValue) and the previous value (oldValue) can be accessed for monitoring and responding to changes.
- This customization allows you to track and react to changes in the currentSpeed property within the Car subclass.
Overriding properties allows us to customize and modify how inherited properties are accessed or observed according to the needs of the subclass.
Final Property
Swift allows us to prevent a subclass from overriding a method, property, or subscript of a superclass by using the final modifier. We write the final keyword before the declaration of the characteristic that we want to protect from overriding (such as final var, final func, final class func and final subscript). If a subclass tries to override a final characteristic, it will cause a compile-time error.
Below is the Swift program to implement Final property:
Swift
class Vehicle
{
final var currentSpeed = 0.0
}
class Car: Vehicle
{
}
|
Explanation:
- In this code, the currentSpeed property of the Vehicle class is marked as final.
- The final keyword indicates that no subclass can override this property.
- If an attempt is made to override this property in the Car class or any other subclass, it will result in a compilation error.
- Marking a property or method as final prevents further modification in subclasses, providing a way to enforce the immutability or specific behavior of that element.
Using the final modifier helps us ensure that a superclass characteristic’s behavior and functionality are consistent and reliable across all subclasses.
Conclusion
In this article, we have learned about inheritance in Swift and how to use it to create subclasses, override methods and properties, and implement polymorphism. Inheritance is a powerful feature of object-oriented programming that allows us to reuse and extend existing code and functionality. It also helps us to model real-world objects and relationships in our programs. We have also seen some of the types of inheritance and the is-a relationship that exists between classes. We have also learned how to use the super keyword to access the superclass characteristics from the subclass and how to use the final keyword to prevent overriding. We hope this article has helped you to understand the concept of inheritance in Swift better.
Share your thoughts in the comments
Please Login to comment...