What polymorphism means
Polymorphism, from the Greek for "many forms", lets one piece of code work with objects of different classes. You write the call once, and Swift runs the version of the method that belongs to the object's actual class.
The idea needs a parent class and subclasses that override one of its methods. Here Animal defines speak(), and Dog and Cat each replace it:
class Animal {
func speak() {
print("...")
}
}
class Dog: Animal {
override func speak() {
print("Woof")
}
}
class Cat: Animal {
override func speak() {
print("Meow")
}
}
Any code that receives an Animal can call speak() without knowing which kind it holds. That is the payoff: a function written today keeps working when you add a Cow subclass tomorrow, with no edits to the function.
Static type and dynamic type
Every variable has a type written in the code, its static type. The object it refers to has a real class at run time, its dynamic type. With a subclass instance stored under the parent type, the two differ:
let pet: Animal = Dog()
pet.speak()
print(type(of: pet))
Woof
Dog
The compiler only knows pet is an Animal, so it only lets you use what Animal offers. At run time the call goes to Dog.speak(), because that is what the object is. Swift picks the method from the dynamic type.
Swift allows the assignment because a Dog is an Animal: a subclass instance can be used wherever its superclass type is expected. No cast is needed.
Collections of mixed types
Polymorphism is most useful with a collection. An array typed [Animal] can hold a Dog next to a Cat:
func roll(_ animals: [Animal]) {
for a in animals { a.speak() }
}
let animals: [Animal] = [Dog(), Cat()]
roll(animals)
roll(animals + [Cow()])
This prints Woof and Meow, then Woof, Meow and Moo, assuming Cow is another subclass that overrides speak() to print Moo. The loop inside roll never mentions a specific class.
The type annotation matters. Without it, [Dog(), Cat()] still works, because Swift infers the shared parent Animal as the element type, but writing [Animal] states the intent and keeps the array open for other subclasses.
The lesson's exercise
The starter code already contains Animal, Dog and Cat with their overrides. The only work is inside mainFunction of the Solution class. The output must be Woof and then Meow.
- Declare the array. Make a constant
animalsof type[Animal]holding aDog()and aCat(), in that order. Aletis enough, since nothing is added later. - Loop over it. Write
for a in animalsand calla.speak()inside. The first pass reachesDog.speak()and the second reachesCat.speak().
The complete solution:
class Animal {
func speak() {
print("...")
}
}
class Dog: Animal {
override func speak() {
print("Woof")
}
}
class Cat: Animal {
override func speak() {
print("Meow")
}
}
class Solution {
func mainFunction() {
let animals: [Animal] = [Dog(), Cat()]
for a in animals {
a.speak()
}
}
}
If the loop printed ... twice, the calls would be using Animal's own method. That does not happen in Swift for overridden class methods, which is the point of the exercise.
Swift details that matter
- Overrides need
override. Writingfunc speak()in a subclass without the keyword is a compile error, which protects you from accidentally replacing a method. - Class methods are dynamically dispatched. Unlike in some languages, you do not mark the parent method with
virtual. Swift decides at run time for methods of a class, unless the method or class is markedfinal, which forbids overriding. - Not an override means no dispatch. A subclass that does not override
speak()inherits the parent's version. APlain: Animalsubclass with an empty body prints.... - Checking the real class. Use
isto test it andas?to get an optional of the subclass type.pet as? Doggives aDog?holding the object, whilepet as? Catgivesnil. Needing this often suggests the behavior should be an overridden method instead. - Structs have no subclasses. Subclass polymorphism belongs to classes. For structs and enums, polymorphism comes through protocols, which a later lesson covers.
- Protocol extensions can surprise you. When a protocol adds a method in an extension without declaring it as a requirement, a call through the protocol type uses the extension's version even if the concrete type defines its own. Declare the method in the protocol itself to get dynamic behavior.
Key takeaways
- Polymorphism means the same call runs different code depending on the object's actual class.
- A subclass instance can be stored under its superclass type, such as
[Animal]holding aDogand aCat. - Write code against the parent type, and new subclasses work without changes.
- Swift needs
overridein the subclass and no special marker in the parent.
