Inheritance
Inheritance allows a class to adopt methods, properties, and other features from another class. The inheriting class is called a subclass, and the class it inherits from is its superclass. This mechanism helps differentiate classes and promotes code reuse in Swift.
Swift ensures overrides are accurate by verifying that they match the superclass definitions. Subclasses can also add observers to inherited properties to detect value changes, whether those properties are stored or computed.
Creating a Base Class
A class without a superclass is a base class.
Note: Unlike some languages, Swift classes don't inherit from a shared universal base. Any class without a specified superclass becomes a base class.
Here's a base class Transport with a stored property currentVelocity (default 0.0, type Double). It includes a read-only computed property description for a string summary, and a makeSound() method that does nothing by default (subclasses can customize it):
class Transport {
var currentVelocity = 0.0
var description: String {
return "moving at \(currentVelocity) km/h"
}
func makeSound() {
// Default: no sound for a generic transport
}
}Create an instance like this:
let basicTransport = Transport()
print("Transport: \(basicTransport.description)")
// Output: Transport: moving at 0.0 km/hThis base class outlines general transport traits but needs subclasses for specifics.
Building Subclasses
Subclassing creates a new class based on an existing one, inheriting its features while allowing additions or modifications.
Syntax: class NewSubclass: ExistingSuperclass { /* details */ }
Example subclass Cycle from Transport:
class Cycle: Transport {
var hasCarrier = false
}Cycle inherits currentVelocity, description, and makeSound(). It adds hasCarrier (default false, type Bool).
Customize after instantiation:
let myCycle = Cycle()
myCycle.hasCarrier = true
myCycle.currentVelocity = 20.0
print("Cycle: \(myCycle.description)")
// Output: Cycle: moving at 20.0 km/hSubclasses can be further subclassed. Here's DuoCycle from Cycle:
class DuoCycle: Cycle {
var passengerCount = 0
}DuoCycle inherits everything and adds passengerCount:
let duo = DuoCycle()
duo.hasCarrier = true
duo.passengerCount = 1
duo.currentVelocity = 18.0
print("DuoCycle: \(duo.description)")
// Output: DuoCycle: moving at 18.0 km/hCustomizing with Overrides
Subclasses can replace inherited methods, properties, or subscripts with custom versions using the override keyword. This ensures intent and compiler checks for matches.
Use super to access the superclass version in your override, refining or extending it.
- For methods: Call
super.someMethod(). - For properties: Use
super.somePropertyin getters/setters. - For subscripts: Access
super[someIndex].
Overriding Methods
Provide a new implementation for an inherited method.
Example: RailEngine subclass overrides makeSound():
class RailEngine: Transport {
override func makeSound() {
print("Toot Toot")
}
}Usage:
let engine = RailEngine()
engine.makeSound()
// Output: Toot TootOverriding Properties
Customize getters, setters, or add observers to inherited properties.
Custom Getters and Setters
Override any inherited property, regardless of its original type (stored or computed). Match name and type exactly. You can make a read-only property read-write, but not vice versa.
Note: Overrides with setters must include getters. To keep the value unchanged in the getter, return
super.someProperty.
Example: WheeledVehicle subclass adds transmission and overrides description:
class WheeledVehicle: Transport {
var transmission = 1
override var description: String {
return super.description + " in transmission \(transmission)"
}
}Usage:
let vehicle = WheeledVehicle()
vehicle.currentVelocity = 40.0
vehicle.transmission = 2
print("WheeledVehicle: \(vehicle.description)")
// Output: WheeledVehicle: moving at 40.0 km/h in transmission 2Adding Property Observers
Observe changes to inherited properties using willSet or didSet, even if originally computed.
Note: Can't add observers to constants or read-only computed properties. If providing a setter, observe changes there instead of separately.
Example: SmartVehicle adjusts transmission based on currentVelocity:
class SmartVehicle: WheeledVehicle {
override var currentVelocity: Double {
didSet {
transmission = Int(currentVelocity / 15.0) + 1
}
}
}Usage:
let smart = SmartVehicle()
smart.currentVelocity = 50.0
print("SmartVehicle: \(smart.description)")
// Output: SmartVehicle: moving at 50.0 km/h in transmission 4Blocking Overrides
Mark methods, properties, or subscripts as final to prevent subclass overrides (e.g., final func someMethod()). Extensions can also use final.
Mark entire classes as final class to block subclassing. Attempts trigger compile-time errors.