The idea
Many things are a kind of something else: a puppy is a dog, a manager is an employee. Inheritance models that "is-a" relationship. A subclass receives all the properties and methods of its superclass, and may add its own. You reuse code instead of copying it.
Syntax
In Swift, the superclass is named after a colon:
class Puppy: Dog {
func whine() {
print("whimper")
}
}
A Puppy now has the name property and the bark() method defined in Dog, plus its own whine(). A Puppy can also be used wherever a Dog is expected:
let pup = Puppy(name: "Buddy")
let dog: Dog = pup
dog.bark() // Woof! I am Buddy
The reverse doesn't hold. A variable declared as Dog only exposes what Dog has, so dog.whine() fails with value of type 'Dog' has no member 'whine', even though the object behind it is a puppy.
Initializers and inheritance
A subclass that adds no stored properties and defines no initializer inherits its superclass's initializers automatically, so Puppy(name: "Buddy") would work without writing anything. The lesson, however, writes the initializer explicitly, to show how a subclass hands its arguments upward:
class Puppy: Dog {
override init(name: String) { super.init(name: name) }
}
Two parts need explaining:
super.init(name: name)calls the superclass initializer.Dogowns the storedname, soDog's initializer has to set it.overrideis required becausePuppydeclares an initializer with exactly the same signature as one inDog. Leaving it out is an error:overriding declaration requires an 'override' keyword.
When a subclass has its own stored properties, its initializer must set them first and call super.init after, so that every property is initialized before the object is used.
The exercise
Below Dog, declare Puppy as a subclass, with the overriding initializer and a whine method that prints whimper:
class Dog {
var name: String
init(name: String) { self.name = name }
func bark() {
print("Woof! I am \(name)")
}
}
class Puppy: Dog {
override init(name: String) { super.init(name: name) }
func whine() {
print("whimper")
}
}
class Solution {
func mainFunction() {
let buddy = Puppy(name: "Buddy")
buddy.bark()
buddy.whine()
}
}
The output is:
Woof! I am Buddy
whimper
bark was never written in Puppy; it comes from Dog.
What is shared and what is not
Inheritance copies nothing: a Puppy object is one object that has the parts defined in both classes. Its name storage comes from Dog, and its whine() comes from Puppy. A class can have only one superclass in Swift, but the chain can be long: a Puppy can itself be the parent of a TinyPuppy, which then has everything from all three classes.
Because the superclass defines the shared part, a change to Dog.bark() shows up in every subclass that doesn't replace it. That is the benefit of reuse, and also the reason to inherit only when the relationship is truly "is-a".
Common mistakes
- Omitting
overrideon the initializer when the signature matches the parent's. - Forgetting
super.initwhen the initializer is written out by hand and the superclass has properties to set. - Expecting subclass methods through a superclass variable. The declared type decides which members you can call.
- Subclassing a
finalclass. A class markedfinalcan't be inherited from; the compiler saysinheritance from a final class 'Z'. - Using inheritance for convenience alone. It should model a real "is-a" relationship.
Key takeaways
- Write
class Child: Parentto inherit properties and methods. - A subclass adds its own methods and properties.
- Pass arguments to the parent with
super.init(...); useoverridewhen the initializer repeats the parent's signature. - A subclass object can be used wherever the parent type is expected.
