Inheritance
|
This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline. This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
A class can inherit methods, properties, subscripts and other characteristics from another class. Inheritance is what most fundamentally distinguishes classes from the other named types — structs and enums cannot inherit from another struct or enum at all.
Base Classes and Subclassing
class Animal {
let name: String
var energy = 100
init(name: String) {
self.name = name
}
func makeSound() -> String {
"..."
}
var summary: String {
"\(name) says \(makeSound())"
}
}
class Dog: Animal {
var breed: String
init(name: String, breed: String) {
self.breed = breed
super.init(name: name) // a subclass's own storage is set first, then the superclass's
}
}
let generic = Animal(name: "Generic Animal")
let rex = Dog(name: "Rex", breed: "Labrador")
print(rex.name, rex.breed) // Dog inherits `name` and `energy` from Animal for free
A class with no explicit superclass (Animal above) is a base class — Swift classes do not implicitly
inherit from a universal root class the way Objective-C classes inherit from NSObject. Writing : Animal after
Dog makes it a subclass of Animal, inheriting every method, property and subscript Animal declares; a
subclass may add its own storage and behavior on top, and a subclass initializer that adds stored properties must
call super.init(…) to let the superclass finish initializing its own storage (see
Initialization and Deinitialization
for the full delegation and safety-check rules this feeds into).
Overriding Methods, Properties and Observers, and Calling super
class Cat: Animal {
override func makeSound() -> String { // `override` replaces the inherited implementation
"Meow"
}
override var summary: String { // computed properties can be overridden too
super.summary + " (a cat, obviously)" // `super.` reaches the superclass's version
}
override var energy: Int {
didSet { // adding an observer to an *inherited* property
if energy < oldValue {
print("\(name) used some energy, now at \(energy)")
}
}
}
}
let whiskers = Cat(name: "Whiskers")
print(whiskers.summary) // Whiskers says Meow (a cat, obviously)
whiskers.energy -= 10 // Whiskers used some energy, now at 90
override is required whenever a subclass redeclares an inherited method, subscript, or property (the compiler
rejects an accidental redeclaration missing the keyword, and rejects override on a member that overrides
nothing). A property override may change nothing but add observers to an inherited stored or computed property,
as Cat.energy does above — the inherited implementation still runs first, and the override’s willSet/didSet
runs in addition to it. super.<member> reaches the superclass’s own implementation of a method, computed
property, or subscript from inside an override — most commonly to extend rather than fully replace the inherited
behavior, as summary does above.
final, Dynamic Dispatch and Polymorphism
class Base {
final func cannotBeOverridden() -> String { "fixed behavior" } // `final` on a member: no subclass may override it
}
final class Sealed: Base {} // `final` on the class itself: no subclass of Sealed may ever be declared
let animals: [Animal] = [generic, rex, whiskers]
for animal in animals {
print(animal.summary) // each call dispatches to the *runtime* type's implementation, not `Animal`'s
}
Marking a method, property or subscript final forbids any subclass from overriding it; marking the class itself
final forbids subclassing it at all — both are optimization and API-design tools: a final member can be
dispatched statically instead of dynamically, and a sealed hierarchy can’t be extended in ways its author didn’t
anticipate. Calling an overridable member through a superclass-typed reference or array element (animal.summary
above) is dynamic dispatch: Swift looks up the actual runtime type of the instance and calls its implementation,
which is what lets a loop over [Animal] produce `Dog’s, `Cat’s and `Animal’s own behavior side by side — polymorphism.
When to Subclass vs. Compose or Use Protocols
The Animal/Dog/Cat hierarchy above is small enough to be harmless, but it already shows inheritance’s known
problems at scale: a subclass inherits everything from its superclass whether it wants it or not (a RobotDog
would still inherit energy, which makes no sense for a machine); Swift classes support only single
inheritance, so once a type needs to combine "makes a sound" with "can be drawn on screen" and "can be encoded to
JSON", a class hierarchy alone cannot express all three without one of them living awkwardly inside another; and
a change to a base class’s implementation can silently break a distant subclass that depended on the old
behavior (the fragile base class problem) — worse the deeper the hierarchy grows.
Composition (a type holding another type as a property and delegating to it, rather than inheriting from it) and protocols (declaring a set of requirements any type — class, struct, or enum — can adopt, with no implementation and no single-inheritance limit) sidestep all three: prefer composing or adopting a protocol over subclassing whenever the relationship is "has a" or "can do", and reserve subclassing for a genuine "is a" relationship where sharing a base class’s stored state and initializers is actually wanted. This is the motivation behind protocol-oriented programming, covered in full in Protocols.
See Also
-
Structures and Classes — why only classes, not structs or enums, can inherit.
-
Initialization and Deinitialization — designated/convenience initializers, two-phase initialization, and
deinitacross a class hierarchy. -
Protocols — composing behavior without inheritance, and protocol-oriented design.
-
Methods and Subscripts —
self, type methods, and subscripts, which override the same way instance methods do. -
Type Casting and Reflection — checking and narrowing a value’s type across a class hierarchy with
is/as?/as!.