Classes Practice
Module Summary
Congratulations on completing the Classes module! Let's practice everything we've learned with comprehensive exercises.
What You've Learned
Definition, properties, methods
Shared instances, identity
Superclass, subclass, override
Designated, convenience, required
Cleanup before deallocation
is, as?, as!, Any
Strong, weak, unowned
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
// 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!