Skip to content

Swift OOP

Classes are blueprints for creating objects, and objects are instances of classes

class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let person1 = Person(name: "Alice", age: 25)
print(person1.name) // "Alice"

Classes are reference types, Structures are value types

class Dog {
var name: String
init(name: String) { self.name = name }
}
let dog1 = Dog(name: "Buddy")
let dog2 = dog1 // Both reference same object
dog2.name = "Max"
print(dog1.name) // "Max" (changed!)

Properties store values in classes - can be simple stored values or computed values

class Rectangle {
var width: Double = 0.0 // Stored property
var height: Double = 0.0 // Stored property
var area: Double { // Computed property
return width * height
}
}
let rect = Rectangle()
rect.width = 5.0
rect.height = 10.0
print(rect.area) // 50.0

Code that runs when property values change

class BankAccount {
var balance: Double = 0.0 {
willSet { print("About to set balance to \(newBalance)") }
didSet { print("Balance changed from \(oldValue) to \(balance)") }
}
}
let account = BankAccount()
account.balance = 1000 // Triggers observers

Functions that belong to classes and can access class properties

Methods called on instances of a class

class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
}
let counter = Counter()
counter.increment()
counter.increment(by: 5)
print(counter.count) // 6

Methods called on the class itself, not instances

class MathUtils {
static func square(_ number: Double) -> Double {
return number * number
}
}
print(MathUtils.square(5.0)) // 25.0 - called on class

Creating new classes based on existing classes, inheriting their properties and methods

class Animal { // Base class
func makeSound() {
print("Some generic animal sound")
}
}
class Dog: Animal { // Subclass inherits from Animal
override func makeSound() { // Override base class method
print("Woof!")
}
}
let dog = Dog()
dog.makeSound() // "Woof!" (uses overridden method)

Subclasses can override properties from parent class

class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) km/h"
}
}
class Car: Vehicle {
override var description: String {
return super.description + " in gear"
}
}

Objects of different types can be treated as objects of a common super type

Same method name, different implementations in different classes

class Bird {
func fly() { print("The bird is flying") }
}
class Penguin: Bird {
override func fly() { print("Penguins can't fly!") }
}
class Eagle: Bird {
override func fly() { print("The eagle is soaring high") }
}
let birds: [Bird] = [Bird(), Penguin(), Eagle()]
for bird in birds {
bird.fly() // Different behavior for each type
}

Checking and converting types at runtime

if let penguin = someBird as? Penguin {
penguin.swim() // Only penguins can swim
}

Hiding internal state and requiring all interaction through object’s methods

class BankAccount {
private var balance: Double // Hidden from outside
public let accountHolder: String // Visible to outside
public func deposit(amount: Double) { // Controlled access
if amount > 0 { balance += amount }
}
public func getBalance() -> Double { // Controlled access
return balance
}
}
let account = BankAccount()
account.deposit(amount: 500) // Allowed - public method
// account.balance = 1000 // Error - private property

Hiding complex implementation details and showing only essential features

protocol Shape { // Abstract interface
var area: Double { get }
func draw()
}
class Circle: Shape { // Concrete implementation
var radius: Double
var area: Double { return Double.pi * radius * radius }
func draw() { print("Drawing circle") }
}
class Rectangle: Shape { // Another implementation
var width, height: Double
var area: Double { return width * height }
func draw() { print("Drawing rectangle") }
}
let shapes: [Shape] = [Circle(radius: 5), Rectangle(width: 4, height: 6)]
for shape in shapes {
shape.draw() // Don't care about implementation details
}

The process of preparing an instance of a class for use

class Person {
var name: String
var age: Int
// Designated initializer - main initializer
init(name: String, age: Int) {
self.name = name
self.age = age
}
// Convenience initializer - calls designated initializer
convenience init(name: String) {
self.init(name: name, age: 0)
}
}
let person1 = Person(name: "Alice", age: 25) // Designated
let person2 = Person(name: "Bob") // Convenience

Controlling what code can access which properties and methods

class DataManager {
public var apiKey: String // Accessible anywhere
internal var baseURL: String // Accessible in same module (default)
private var secretToken: String // Accessible only in this class
fileprivate var cache: [String] // Accessible in same file
public func fetchData() { } // Public interface
private func validate() { } // Internal helper
}

Common solutions to recurring design problems

Only one instance of a class exists

class AppSettings {
static let shared = AppSettings() // Single instance
private init() {} // Prevent creating others
var theme: String = "Dark"
}
let settings = AppSettings.shared // Always get the same instance
settings.theme = "Light"

Objects notify other objects about changes

class Observable {
var value: String = "" {
didSet {
onValueChanged?(value) // Notify observers
}
}
var onValueChanged: ((String) -> Void)? // Callback
}
let observable = Observable()
observable.onValueChanged = { newValue in
print("Value changed to: \(newValue)")
}
observable.value = "Hello" // Triggers notification

Creating objects without specifying exact class

class VehicleFactory {
static func createVehicle(type: String) -> Vehicle {
switch type {
case "car": return Car()
case "bike": return Bike()
default: return Car()
}
}
}
let vehicle = VehicleFactory.createVehicle(type: "car")
// Don't need to know exact class, just interface