Swift: OOP, Protocols & Extensions
Classes vs Structs
Struct: value type — copied on assignment. Prefer structs for data models (String, Array, Int are all structs)
Class: reference type — shared reference. Use for identity-based models, inheritance, Objective-C interop
Struct is default choice in Swift — safer, no retain cycles, better with SwiftUI
Mutating methods: structs need `mutating` keyword on methods that modify self
// Struct (value type)
struct Point {
var x: Double
var y: Double
mutating func translate(dx: Double, dy: Double) {
x += dx
y += dy
}
func distanceTo(_ other: Point) -> Double {
let dx = x - other.x
let dy = y - other.y
return (dx*dx + dy*dy).squareRoot()
}
}
// Class (reference type)
class Person {
let id: UUID
var name: String
private(set) var age: Int // public get, private set
init(name: String, age: Int) {
self.id = UUID()
self.name = name
self.age = age
}
deinit {
print("\(name) deallocated")
}
}
class Employee: Person {
var department: String
init(name: String, age: Int, department: String) {
self.department = department
super.init(name: name, age: age)
}
override var description: String {
"\(name) in \(department)"
}
}Protocols
// Protocol — like interface in other languages
protocol Drawable {
var color: String { get }
func draw() -> String
func area() -> Double
}
protocol Identifiable {
var id: UUID { get }
}
// Protocol composition
typealias DrawableIdentifiable = Drawable & Identifiable
// Protocol with default implementation (via extension)
protocol Greetable {
var name: String { get }
func greet() -> String
}
extension Greetable {
func greet() -> String {
"Hello, I am \(name)"
}
}
// Conformance
struct Circle: Drawable {
let radius: Double
var color: String
func draw() -> String { "Circle(r=\(radius))" }
func area() -> Double { .pi * radius * radius }
}
// Protocol as type (existential)
let shapes: [any Drawable] = [Circle(radius: 5, color: "red")]
// Generic constraint
func printArea<T: Drawable>(_ shape: T) {
print("Area: \(shape.area())")
}Extensions
// Add methods to existing types
extension String {
var isPalindrome: Bool {
self == String(self.reversed())
}
func truncated(to length: Int, ellipsis: String = "...") -> String {
guard self.count > length else { return self }
return String(self.prefix(length)) + ellipsis
}
}
"racecar".isPalindrome // true
"Hello, World!".truncated(to: 5) // "Hello..."
// Retroactive conformance
extension Int: Drawable {
var color: String { "black" }
func draw() -> String { "\(self)" }
func area() -> Double { 0 }
}
// Computed properties on Collection
extension Collection {
var isNotEmpty: Bool { !isEmpty }
}
// Constrained extensions
extension Array where Element: Comparable {
func isSorted() -> Bool {
zip(self, dropFirst()).allSatisfy { $0 <= $1 }
}
}Property Wrappers & Result Builders
// Property wrappers (power SwiftUI @State, @Published, etc.)
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
init(wrappedValue: T, _ range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
struct Player {
@Clamped(0...100) var health: Int = 100
}
var player = Player()
player.health = 150 // clamped to 100
player.health = -10 // clamped to 0
// Combine — reactive framework
import Combine
class ViewModel: ObservableObject {
@Published var searchText = ""
@Published var results: [String] = []
private var cancellables = Set<AnyCancellable>()
init() {
$searchText
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
.removeDuplicates()
.sink { [weak self] text in
self?.search(text)
}
.store(in: &cancellables)
}
}Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free