Lesson 70Classes

Classes Practice

Module Summary

Congratulations on completing the Classes module! Let's practice everything we've learned with comprehensive exercises.

What You've Learned

Class Basics

Definition, properties, methods

Reference Types

Shared instances, identity

Inheritance

Superclass, subclass, override

Initializers

Designated, convenience, required

Deinitializers

Cleanup before deallocation

Type Casting

is, as?, as!, Any

ARC & Memory

Strong, weak, unowned

Class vs Struct

When to use each

Practice Exercises

1. Shape Hierarchy

Build a class hierarchy with Shape base class and Circle, Rectangle subclasses. Override area() method.

2. Employee System

Create Employee base class with Manager and Developer subclasses. Use type casting to call subclass-specific methods.

3. Memory Management

Build Author-Book relationship with proper weak references to avoid retain cycles.

4. Singleton Pattern

Implement AppSettings as a singleton with shared instance.

5. Delegate Pattern

Create DataLoader with delegate protocol for async communication.

Common Patterns

Singleton: Single shared instance (AppSettings.shared)
Delegate: Communication between objects
Factory: Create objects without specifying exact class
Observer: Notify multiple objects of state changes
practice.swift
// CLASSES PRACTICE
// Comprehensive exercises covering all class concepts

// ===== EXERCISE 1: Shape Hierarchy =====
// Create a shape class hierarchy with inheritance

class Shape {
    var name: String

    init(name: String) {
        self.name = name
    }

    func area() -> Double {
        return 0
    }

    func describe() -> String {
        return "\(name) with area \(area())"
    }
}

class Circle: Shape {
    var radius: Double

    init(radius: Double) {
        self.radius = radius
        super.init(name: "Circle")
    }

    override func area() -> Double {
        return Double.pi * radius * radius
    }
}

class Rectangle: Shape {
    var width: Double
    var height: Double

    init(width: Double, height: Double) {
        self.width = width
        self.height = height
        super.init(name: "Rectangle")
    }

    override func area() -> Double {
        return width * height
    }
}

let shapes: [Shape] = [
    Circle(radius: 5),
    Rectangle(width: 4, height: 6),
    Circle(radius: 3)
]

for shape in shapes {
    print(shape.describe())
}

// ===== EXERCISE 2: Employee System =====
// Practice inheritance and type casting

class Employee {
    var name: String
    var salary: Double

    init(name: String, salary: Double) {
        self.name = name
        self.salary = salary
    }

    func work() {
        print("\(name) is working")
    }

    func getBonus() -> Double {
        return salary * 0.1
    }
}

class Manager: Employee {
    var teamSize: Int

    init(name: String, salary: Double, teamSize: Int) {
        self.teamSize = teamSize
        super.init(name: name, salary: salary)
    }

    override func work() {
        print("\(name) is managing a team of \(teamSize)")
    }

    override func getBonus() -> Double {
        return salary * 0.2  // Managers get 20% bonus
    }

    func holdMeeting() {
        print("\(name) is holding a team meeting")
    }
}

class Developer: Employee {
    var language: String

    init(name: String, salary: Double, language: String) {
        self.language = language
        super.init(name: name, salary: salary)
    }

    override func work() {
        print("\(name) is coding in \(language)")
    }

    func pushCode() {
        print("\(name) pushed code to repository")
    }
}

let team: [Employee] = [
    Manager(name: "Alice", salary: 100000, teamSize: 5),
    Developer(name: "Bob", salary: 80000, language: "Swift"),
    Developer(name: "Charlie", salary: 75000, language: "Python")
]

// Type casting in action
for employee in team {
    employee.work()

    if let manager = employee as? Manager {
        manager.holdMeeting()
    } else if let dev = employee as? Developer {
        dev.pushCode()
    }
}

// ===== EXERCISE 3: Memory Management =====
// Practice weak/unowned references

class Author {
    let name: String
    var books: [Book] = []

    init(name: String) {
        self.name = name
        print("Author \(name) created")
    }

    func write(title: String) {
        let book = Book(title: title, author: self)
        books.append(book)
    }

    deinit {
        print("Author \(name) deinitialized")
    }
}

class Book {
    let title: String
    weak var author: Author?  // Weak to avoid cycle

    init(title: String, author: Author) {
        self.title = title
        self.author = author
        print("Book '\(title)' created")
    }

    deinit {
        print("Book '\(title)' deinitialized")
    }
}

var author: Author? = Author(name: "J.K. Rowling")
author?.write(title: "Harry Potter")
author?.write(title: "Fantastic Beasts")
author = nil  // All objects properly deallocated!

// ===== EXERCISE 4: Singleton Pattern =====
class AppSettings {
    static let shared = AppSettings()

    var theme: String = "light"
    var fontSize: Int = 14
    var notificationsEnabled: Bool = true

    private init() { }  // Private to prevent external instantiation

    func reset() {
        theme = "light"
        fontSize = 14
        notificationsEnabled = true
    }
}

// Usage
AppSettings.shared.theme = "dark"
AppSettings.shared.fontSize = 16

// ===== EXERCISE 5: Delegate Pattern =====
protocol DataLoaderDelegate: AnyObject {
    func didStartLoading()
    func didFinishLoading(data: [String])
    func didFailWithError(_ error: String)
}

class DataLoader {
    weak var delegate: DataLoaderDelegate?

    func loadData() {
        delegate?.didStartLoading()

        // Simulate async data loading
        let success = true

        if success {
            delegate?.didFinishLoading(data: ["Item 1", "Item 2", "Item 3"])
        } else {
            delegate?.didFailWithError("Network error")
        }
    }
}

class ViewController: DataLoaderDelegate {
    let loader = DataLoader()

    init() {
        loader.delegate = self
    }

    func fetchData() {
        loader.loadData()
    }

    func didStartLoading() {
        print("Loading started...")
    }

    func didFinishLoading(data: [String]) {
        print("Received data: \(data)")
    }

    func didFailWithError(_ error: String) {
        print("Error: \(error)")
    }
}

let vc = ViewController()
vc.fetchData()

Module Complete!

You've mastered Swift Classes! You now understand inheritance, polymorphism, memory management, and when to choose classes over structs.

Next up: Protocols - define blueprints for methods, properties, and requirements!