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


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:

SWIFT
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

SWIFT
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

100%
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

SWIFT
// ============================================
// 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

SWIFT
// ============================================
// 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:

SWIFT
// ============================================
// 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 5

Tip: 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

SWIFT
// ============================================
// 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:

100%
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

SWIFT
// ============================================
// 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

SWIFT
// ============================================
// 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

س Does Swift support multiple inheritance?
ج No. Swift is a single-inheritance language (one subclass, one superclass). Multiple inheritance needs are fulfilled through protocols — a class can conform to multiple protocols.
س Why must I sometimes call super when overriding a method?
ج It's not mandatory, but if you don't call 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.
س Are final class and struct the same regarding inheritance prevention?
ج Behaviorally yes — neither can be subclassed. But struct is a value type and class is a reference type. Use final class to prevent inheritance while keeping reference semantics. If you need neither inheritance nor reference semantics, use struct.
س What's the difference between upcasting and downcasting?
ج Upcasting treats a subclass object as a superclass type (automatic and implicit, e.g. 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.
س Why must I initialize my own stored properties before calling super.init?
ج This is Swift's two-phase initialization safety mechanism. Phase 1: initialize stored properties from subclass to superclass. Phase 2: customize from superclass to subclass. This ensures all stored properties have definite values before the superclass init can access them, preventing uninitialized state.

📖 Summary


📝 Exercises

  1. Basic: Create a Person base class (properties: name, age; method: introduce()), then create Student and Teacher subclasses that override introduce() and add their own properties (studentId, subject).
  2. Intermediate: Design a MediaItem base class with a title property and a play() method. Create Movie (extra: director) and Song (extra: artist) subclasses that override play(). Use a polymorphic array to play them all.
  3. Challenge: Implement a "Permission Control System": base class User with permissionLevel property and canAccess(_:) method. Create AdminUser, EditorUser, ViewerUser subclasses. AdminUser uses a final method override func canAccess(_:) that always returns true. Use a polymorphic array to check access for each user uniformly.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%