Swift Polymorphism
Polymorphism lets you treat objects of different but related types through a single common interface, so the correct implementation runs automatically at call time.
What Is Polymorphism
Polymorphism means "many forms": a single piece of code can operate on values of several different concrete types as long as they share a common supertype or protocol. In Swift this shows up whenever you store subclass instances in an array of their superclass type, or store different conforming types in an array of a shared protocol type.
Dynamic Dispatch with Classes
When you call an overridden method through a superclass-typed reference, Swift looks up the actual runtime type of the instance and calls that type's implementation. This is called dynamic dispatch, and it's what makes a loop over [Employee] automatically call Manager.pay() or Developer.pay() depending on what's really stored in each array slot.
- Subtype polymorphism: a subclass instance can be used wherever its superclass type is expected.
- Protocol-based polymorphism: any conforming type can be used wherever the protocol type is expected.
- Parametric polymorphism: generic functions and types work uniformly across many types via type parameters.
- Ad-hoc polymorphism: function overloading lets the same function name behave differently per parameter type.
Polymorphism through class inheritance
class Employee {
let name: String
init(name: String) { self.name = name }
func pay() -> Double { 0 }
}
class Manager: Employee {
override func pay() -> Double { 6000 }
}
class Developer: Employee {
override func pay() -> Double { 5000 }
}
let staff: [Employee] = [Manager(name: "Asha"), Developer(name: "Ben")]
for person in staff {
print("\(person.name) earns \(person.pay())")
}
// Asha earns 6000.0
// Ben earns 5000.0Polymorphism through a protocol
protocol Shape {
func area() -> Double
}
struct Circle: Shape {
var radius: Double
func area() -> Double { .pi * radius * radius }
}
struct Rectangle: Shape {
var width: Double
var height: Double
func area() -> Double { width * height }
}
let shapes: [Shape] = [Circle(radius: 2), Rectangle(width: 3, height: 4)]
let totalArea = shapes.reduce(0) { $0 + $1.area() }
print("Total area: \(totalArea)")Type Casting: is, as?, and as!
When a collection is typed as a common supertype or protocol, you sometimes need to recover the specific concrete type. The is operator checks a type without casting, as? performs a safe, optional downcast, and as! forces the downcast and crashes if it's wrong.
Safely downcasting with as?
class Media { let title: String; init(title: String) { self.title = title } }
class Movie: Media { var director = "" }
class Song: Media { var artist = "" }
let library: [Media] = [Movie(title: "Interstellar"), Song(title: "Yesterday")]
for item in library {
if let movie = item as? Movie {
print("\(movie.title) is a movie")
} else if let song = item as? Song {
print("\(song.title) is a song")
}
}Exercise: Swift Polymorphism
What does polymorphism mean in Swift's class hierarchies?