Swift: وراثة Swift والتعددية
Inheritance is one of the three pillars of object-oriented programming, allowing one class to build on another, reusing and extending existing behavior. This lesson dives into Swift's inheritance mechanism and the application of polymorphism.
1. What You'll Learn
- Creating subclasses through inheritance
- Overriding methods, properties, and subscripts with
override - Calling parent class implementations with
super - Preventing inheritance and overriding with
final - Upcasting and the runtime mechanics of polymorphism
2. A Real-World UI Developer Story
(1) Pain Point: Massive Code Duplication Across UI Components
Charlie is building a UI component library requiring Button, Label, TextField, and more — all with similar fundamental behavior:
class Button {
var frame: CGRect
var backgroundColor: UIColor
func render() { /* Draw background */ }
func handleTap() { /* Handle tap */ }
}
class Label {
var frame: CGRect
var backgroundColor: UIColor
func render() { /* Draw background */ }
}
frame, backgroundColor, and render() are nearly identical in Button and Label. Every new component copies and pastes the same properties; changing a base behavior requires editing 10 files.
(2) Solution: Inheritance
class UIView {
var frame: CGRect
var backgroundColor: UIColor
func render() { /* Draw base background */ }
}
class Button: UIView {
override func render() {
super.render() // First, draw background
// Then draw button-specific styling
}
func handleTap() { }
}
class Label: UIView {
override func render() {
super.render()
// Then draw label-specific styling
}
}
Common properties are defined once and reused by all subclasses. Changing base behavior requires editing only one file.
(3) Benefit: Reuse + Extensibility
| Dimension | Copy-Paste Style | Inheritance |
|---|---|---|
| Code reuse | 0% — each component independent | 100% — base properties and methods |
| Change cost | Edit 10 files | Edit 1 base class |
| New component | Copy entire file | Inherit + add differences |
| Consistency | Easy to miss edits | Base class guarantees consistent behavior |
| Extensibility | Override risk high | override + super is controllable |
3. Inheritance Basics
(1) Subclassing
graph TB
A[Animal] --> B["Properties: name, age"]
A --> C["Method: makeSound()"]
B --> D[Dog]
C --> D
D --> E["Property: breed"]
D --> F["Override: makeSound()"]
A --> G[Cat]
G --> H["Property: color"]
G --> I["Override: makeSound()"]
| Concept | Description |
|---|---|
| Base class / Superclass | The class being inherited from; in Swift all classes ultimately derive from AnyObject |
| Subclass | A class that inherits from a base class; can add new properties and methods |
| Single inheritance | Swift only supports single inheritance (one subclass, one superclass) |
| Root class | A class with no superclass is called a root class |
▶ Example: Basic Inheritance
// ============================================
// Basic inheritance usage
// ============================================
// Base class
class Vehicle {
var make: String
var model: String
var year: Int
init(make: String, model: String, year: Int) {
self.make = make
self.model = model
self.year = year
}
func description() -> String {
return "\(year) \(make) \(model)"
}
func startEngine() {
print("Engine started")
}
}
// Subclass — inherits from Vehicle
class Car: Vehicle {
var numberOfDoors: Int
init(make: String, model: String, year: Int, numberOfDoors: Int) {
self.numberOfDoors = numberOfDoors
super.init(make: make, model: model, year: year) // Call superclass initializer
}
func honk() {
print("Beep beep!")
}
}
// Subclass — inherits from Vehicle
class Motorcycle: Vehicle {
var hasSidecar: Bool
init(make: String, model: String, year: Int, hasSidecar: Bool = false) {
self.hasSidecar = hasSidecar
super.init(make: make, model: model, year: year)
}
func wheelie() {
print("Doing a wheelie!")
}
}
let car = Car(make: "Toyota", model: "Camry", year: 2022, numberOfDoors: 4)
print(car.description()) // Inherited method
car.honk() // Car's own method
Output:
TEXT 📖 للعرض فقط2022 Toyota Camry Beep beep!
4. Overriding and super
(1) Overriding with override
A subclass can override a superclass method, computed property, or subscript using override:
| Overridable | override Required |
Notes |
|---|---|---|
| Instance methods | Yes — must add | Provides a new function body |
| Computed properties | Yes — must add | Provides new getter/setter |
| Subscripts | Yes — must add | Provides new subscript implementation |
| Stored properties | No — cannot | Stored properties cannot be overridden |
▶ Example: Overriding Methods
// ============================================
// Overriding superclass methods with override
// ============================================
class Animal {
var name: String
init(name: String) { self.name = name }
func makeSound() {
print("\(name) makes a sound")
}
}
class Dog: Animal {
override func makeSound() {
print("\(name) barks: Woof!")
}
}
class Cat: Animal {
override func makeSound() {
print("\(name) meows: Meow!")
}
}
let dog = Dog(name: "Buddy")
let cat = Cat(name: "Whiskers")
dog.makeSound()
cat.makeSound()
Output:
TEXT 📖 للعرض فقطBuddy barks: Woof! Whiskers meows: Meow!
(2) Calling the Superclass with super
Use super inside an override to call the superclass version:
// ============================================
// Calling superclass with the super keyword
// ============================================
class Shape {
var color: String
init(color: String) {
self.color = color
print("Shape init: \(color)")
}
func draw() {
print("Drawing a shape in \(color)")
}
}
class Circle: Shape {
var radius: Double
init(color: String, radius: Double) {
self.radius = radius
super.init(color: color) // Complete own init first, then call super
}
override func draw() {
super.draw() // First, execute superclass drawing
print(" It's a circle with radius \(radius)")
}
}
let circle = Circle(color: "Red", radius: 5.0)
circle.draw()
Output:
TEXT 📖 للعرض فقطShape init: Red Drawing a shape in Red It's a circle with radius 5Tip: During initialization, a subclass must first complete assigning its own stored properties before calling
super.init. This is Swift's two-phase initialization safety mechanism.
5. final and Polymorphism
(1) final Prevents Inheritance
The final keyword can prevent a class from being subclassed or a method/property from being overridden:
| Usage | Effect |
|---|---|
final class |
This class cannot be subclassed |
final func |
This method cannot be overridden |
final var |
This property cannot be overridden |
▶ Example: Using final
// ============================================
// final prevents inheritance and overriding
// ============================================
class Calculator {
// Basic operations can be overridden
func add(_ a: Int, _ b: Int) -> Int { return a + b }
// Core logic must not be overridden
final func process(_ a: Int, _ b: Int, operation: String) -> String {
let result: Int
switch operation {
case "add": result = add(a, b)
default: result = 0
}
return "Result: \(result)"
}
}
// This subclass can override add, but not process
class AdvancedCalculator: Calculator {
override func add(_ a: Int, _ b: Int) -> Int {
print("Advanced add: \(a) + \(b)")
return a + b
}
// override func process(...) // Compile error!
}
let calc = AdvancedCalculator()
print(calc.process(3, 4, operation: "add"))
Output:
TEXT 📖 للعرض فقطAdvanced add: 3 + 4 Result: 7
(2) Polymorphism
Polymorphism allows a variable of a superclass type to refer to a subclass instance, dynamically dispatching to the subclass's overridden implementation:
graph TB
A["let shapes: [Shape] = [Circle(), Rectangle()]"] --> B["for shape in shapes"]
B --> C["shape.draw() ← polymorphism"]
C --> D["Circle's draw()"]
C --> E["Rectangle's draw()"]
▶ Example: Polymorphism in Practice
// ============================================
// Polymorphism — processing different types uniformly
// ============================================
class Employee {
let name: String
init(name: String) { self.name = name }
func work() -> String {
return "\(name) is working"
}
func bonus() -> Double {
return 0
}
}
class Developer: Employee {
override func work() -> String { return "\(name) is writing code" }
override func bonus() -> Double { return 5000 }
}
class Designer: Employee {
override func work() -> String { return "\(name) is designing UI" }
override func bonus() -> Double { return 3000 }
}
class Manager: Employee {
override func work() -> String { return "\(name) is managing team" }
override func bonus() -> Double { return 8000 }
}
// Polymorphism: process different subclasses uniformly
let team: [Employee] = [
Developer(name: "Alice"),
Designer(name: "Bob"),
Manager(name: "Charlie")
]
var totalBonus = 0.0
for member in team {
print(member.work()) // Dynamically calls each implementation
totalBonus += member.bonus()
}
print("Total bonus: $\(totalBonus)")
Output:
TEXT 📖 للعرض فقطAlice is writing code Bob is designing UI Charlie is managing team Total bonus: $16000.0
6. Complete Example: UI Component Rendering System
// ============================================
// Complete example: UI component rendering system
// Features: Inheritance + overriding + super + final + polymorphism combined
// ============================================
import Foundation
// 1. Base class (subclassable)
class UIComponent {
let id: String
var x: Int
var y: Int
init(id: String, x: Int, y: Int) {
self.id = id
self.x = x
self.y = y
}
func render() -> String {
return "[\(id)] at (\(x), \(y))"
}
// Core logic — must not be overridden
final func display() {
print(render())
}
}
// 2. Button subclass
class Button: UIComponent {
let label: String
init(id: String, x: Int, y: Int, label: String) {
self.label = label
super.init(id: id, x: x, y: y)
}
override func render() -> String {
let base = super.render()
return "\(base) [Button: \(label)]"
}
}
// 3. TextField subclass
class TextField: UIComponent {
var text: String
init(id: String, x: Int, y: Int, text: String = "") {
self.text = text
super.init(id: id, x: x, y: y)
}
override func render() -> String {
return "\(super.render()) [TextField: \"\(text)\"]"
}
}
// 4. Polymorphic rendering
let components: [UIComponent] = [
Button(id: "btn1", x: 10, y: 20, label: "Submit"),
TextField(id: "txt1", x: 10, y: 60, text: "Hello"),
Button(id: "btn2", x: 10, y: 100, label: "Cancel"),
TextField(id: "txt2", x: 10, y: 140)
]
for component in components {
component.display() // Polymorphic call
}
Output:
TEXT 📖 للعرض فقط[btn1] at (10, 20) [Button: Submit] [txt1] at (10, 60) [TextField: "Hello"] [btn2] at (10, 100) [Button: Cancel] [txt2] at (10, 140) [TextField: ""]
❓ FAQ
super, the superclass logic is completely replaced. The typical pattern is to call super first to execute common logic, then add subclass-specific behavior. Whether to call super depends on design intent.final class to prevent inheritance while keeping reference semantics. If you need neither inheritance nor reference semantics, use struct.Employee e = Developer(...)). Downcasting converts a superclass reference back to a subclass type (requires as? or as!), and the conversion may fail, so the result is optional.📖 Summary
- Swift supports only single inheritance — a subclass can inherit from exactly one superclass
- The
overridekeyword overrides superclass methods, computed properties, and subscripts supercalls the superclass implementation inside an overridefinalprevents a class from being subclassed or a method/property from being overridden- Polymorphism lets a superclass reference call a subclass's overridden implementation — "one interface, many behaviors"
- Subclass initialization happens in two phases: subclass stored properties first, then superclass
📝 Exercises
- Basic: Create a
Personbase class (properties:name,age; method:introduce()), then createStudentandTeachersubclasses that overrideintroduce()and add their own properties (studentId,subject). - Intermediate: Design a
MediaItembase class with atitleproperty and aplay()method. CreateMovie(extra:director) andSong(extra:artist) subclasses that overrideplay(). Use a polymorphic array to play them all. - Challenge: Implement a "Permission Control System": base class
UserwithpermissionLevelproperty andcanAccess(_:)method. CreateAdminUser,EditorUser,ViewerUsersubclasses.AdminUseruses afinalmethodoverride func canAccess(_:)that always returns true. Use a polymorphic array to check access for each user uniformly.