Chapter 77: Swift OOP
1. What is OOP? (the clearest possible explanation)
OOP = Object-Oriented Programming is a way of organizing code by grouping data and behavior together into objects.
Instead of writing one giant list of instructions (procedural style), you create small, self-contained things (objects) that know their own data and know how to do things with that data.
Real-life analogy everyone understands:
- Think of a smartphone as an object.
- Data (properties): screen brightness, battery level, model name, owner
- Behavior (methods): takePhoto(), sendMessage(), chargeBattery(), setBrightness(80)
You don’t need to know how the inside works — you just tell the phone what to do.
OOP lets you model your program the same way: create “smart objects” that manage their own state and behavior.
Swift supports OOP very well, but it also encourages value types (structs + enums) and protocol-oriented programming (POP). Still, understanding classic OOP is extremely important — many real APIs, UIKit, SwiftUI patterns, and third-party libraries are built around it.
2. The Four Pillars of OOP — explained in Swift
Pillar 1 – Encapsulation
Encapsulation = “hide the internal details, expose only what is necessary”
In Swift this is done mainly with:
- private / fileprivate / internal / public
- computed properties
- methods that control access
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
class BankAccount { // Private — nobody outside can touch balance directly private var balance: Double = 0.0 // Public read-only property var currentBalance: Double { balance } // Designated initializer init(initialDeposit: Double) { deposit(amount: initialDeposit) } // Public methods — controlled access func deposit(amount: Double) { if amount > 0 { balance += amount print("Deposited ₹\(amount). New balance: ₹\(balance)") } } func withdraw(amount: Double) -> Bool { if amount > 0 && amount <= balance { balance -= amount print("Withdrew ₹\(amount). New balance: ₹\(balance)") return true } else { print("Insufficient funds or invalid amount") return false } } } // Usage — encapsulation in action let myAccount = BankAccount(initialDeposit: 10000) myAccount.deposit(amount: 5000) myAccount.withdraw(amount: 2000) // myAccount.balance = -10000 // ← compile error — private! print("Balance: ₹\(myAccount.currentBalance)") |
Real-life rule:
Make data private Expose behavior (methods) that safely change or read the data
Pillar 2 – Abstraction
Abstraction = “show only the essential features, hide the complexity”
In Swift:
- Use protocols to define what something can do (not how)
- Use classes / structs to hide implementation details
Classic example — shapes:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
protocol Drawable { func draw() var area: Double { get } } class Circle: Drawable { let radius: Double init(radius: Double) { self.radius = radius } func draw() { print("Drawing a circle of radius \(radius)") } var area: Double { Double.pi * radius * radius } } class Rectangle: Drawable { let width: Double let height: Double init(width: Double, height: Double) { self.width = width self.height = height } func draw() { print("Drawing a rectangle \(width)×\(height)") } var area: Double { width * height } } // Abstraction in action let shapes: [any Drawable] = [ Circle(radius: 5), Rectangle(width: 4, height: 6) ] for shape in shapes { shape.draw() print("Area = \(shape.area)") } |
→ The caller doesn’t care whether it’s a circle or rectangle — it only cares that it can draw() and has area.
Pillar 3 – Inheritance
Inheritance = a class can inherit properties & methods from another class (parent → child)
Swift supports single inheritance only (one direct parent).
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
class Animal { let name: String init(name: String) { self.name = name } func makeSound() { print("\(name) makes a sound") } } class Dog: Animal { override func makeSound() { print("\(name) says Woof! 🐶") } func wagTail() { print("\(name) wags tail happily") } } class Cat: Animal { override func makeSound() { print("\(name) says Meow! 🐱") } } let dog = Dog(name: "Bruno") let cat = Cat(name: "Luna") dog.makeSound() // Bruno says Woof! 🐶 cat.makeSound() // Luna says Meow! 🐱 dog.wagTail() // Bruno wags tail happily |
When to use inheritance (modern Swift view):
- Yes: clear “is-a” relationship (Dog is an Animal)
- Yes: you want to share common behavior & state
- No: just to reuse code — prefer composition (has-a) or protocols
Pillar 4 – Polymorphism
Polymorphism = “many forms” — same method name, different behavior depending on the actual object type.
This happens automatically thanks to inheritance + protocols.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
let animals: [Animal] = [Dog(name: "Bruno"), Cat(name: "Luna"), Animal(name: "Generic")] for animal in animals { animal.makeSound() // different sound for each! } // Bruno says Woof! 🐶 // Luna says Meow! 🐱 // Generic makes a sound |
Protocol-based polymorphism (modern Swift favorite):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
protocol Speakable { func speak() } extension Dog: Speakable { func speak() { print("Woof!") } } extension Cat: Speakable { func speak() { print("Meow!") } } let speakers: [any Speakable] = [Dog(name: "Bruno"), Cat(name: "Luna")] speakers.forEach { $0.speak() } |
5. Real-life examples — OOP patterns you will actually use
Example 1 – UIViewController hierarchy (UIKit classic)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class BaseViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setupUI() } func setupUI() { view.backgroundColor = .systemBackground } } class ProfileViewController: BaseViewController { override func setupUI() { super.setupUI() // add profile specific UI title = "Profile" } } |
Example 2 – Payment processor (very common backend / fintech)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
protocol PaymentProcessor { func process(amount: Double) throws -> String } class RazorpayProcessor: PaymentProcessor { func process(amount: Double) throws -> String { // simulate Razorpay return "RZP_TXN_\(UUID().uuidString.prefix(8))" } } class PaytmProcessor: PaymentProcessor { func process(amount: Double) throws -> String { // simulate Paytm return "PTM_\(Int(Date().timeIntervalSince1970))" } } class PaymentService { let processor: PaymentProcessor init(processor: PaymentProcessor) { self.processor = processor } func pay(amount: Double) { do { let transactionID = try processor.process(amount: amount) print("Payment successful! Transaction: \(transactionID)") } catch { print("Payment failed: \(error)") } } } // Usage let service = PaymentService(processor: RazorpayProcessor()) service.pay(amount: 999.99) |
6. Quick Summary – The Four Pillars in Swift
| Pillar | Swift implementation | Real-life feeling |
|---|---|---|
| Encapsulation | private, fileprivate, computed properties | Hide internal state, expose safe methods |
| Abstraction | Protocols, protocol extensions | Define “what” not “how” |
| Inheritance | class Child: Parent | “is-a” relationship (Dog is an Animal) |
| Polymorphism | method overriding + protocol conformance | Same message → different behavior |
7. Small Practice – Try these
- Create a base class Vehicle with makeSound() Create two subclasses: Car (“Vroom!”) and Bike (“Vruum-vruum!”) Put them in an array [Vehicle] and call makeSound() on each
- Create a protocol Payable with method processPayment(amount:) Implement it for two payment methods (Razorpay, Paytm)
- Create a class Employee with name, salary (private) Add method giveRaise(percent:) that safely increases salary
Paste your code here if you want feedback or want to see more elegant versions!
What would you like to explore next?
- Protocol-oriented programming (POP) vs classic OOP
- Struct vs Class — when to choose which
- Inheritance vs composition (modern preference)
- Access control (private, fileprivate, internal, public, open)
- OOP in SwiftUI (ObservableObject, @StateObject…)
- Or move to another topic (optionals, arrays, closures, switch…)
Just tell me — we’ll continue in the same clear, detailed, patient style 😊
