Classes
Swift classes: reference types, inheritance, identity, deinit, and the rules that distinguish class from struct.
Swift — classes
EXAMPLE
// ===== Class basics =====
class User {
var name: String
var email: String
init(name: String, email: String) {
self.name = name
self.email = email
}
}
let u = User(name: "Alex", email: "a@x.io")
u.name = "Sam" // mutation through let — let only locks the REFERENCE, not the object
// ===== Reference vs value semantics =====
let u2 = u
u2.name = "Bob"
print(u.name) // 'Bob' — both refer to the same object
// (Struct would have copied; class shares.)
// ===== Identity =====
let u3 = User(name: "Bob", email: "a@x.io")
u === u2 // true (same reference)
u === u3 // false
// ===== Inheritance (open + final + override) =====
open class Vehicle {
open var description: String { "vehicle" }
open func describe() { print(description) }
}
class Car: Vehicle {
let model: String
init(model: String) { self.model = model }
override var description: String { "car: \(model)" }
}
final class SportsCar: Car { } // cannot be subclassed
// Default classes are NOT open; you must mark 'open' to enable inheritance across modules.
// ===== Initialisers =====
class Animal {
let name: String
init(name: String) { self.name = name }
convenience init() {
self.init(name: "Unknown") // delegates to designated init
}
}
// Required initialisers must be implemented by subclasses:
class Base {
required init() {}
}
class Sub: Base {
required init() { super.init() }
}
// ===== Deinit =====
class Logger {
init() { print("opening log") }
deinit { print("closing log") }
}
// Runs when the last strong reference is released.
// ===== Reference cycles =====
class Parent {
var child: Child?
}
class Child {
weak var parent: Parent? // weak avoids retain cycle
}
// Use 'weak' for back-references; 'unowned' when the lifetime is guaranteed equal.
// ===== Closures + self =====
class ViewController {
var name = "Alex"
func setup() {
someAsync.then { [weak self] in
guard let self else { return }
print(self.name)
}
}
}
// ===== Static + class members =====
class Counter {
static var shared = Counter() // type-level (cannot be overridden)
class var label: String { "counter" } // CAN be overridden by subclass
}
// ===== Final = optimisation + intent =====
// 'final' on classes / methods / properties tells the compiler + reader:
// 'this cannot be subclassed / overridden'. The compiler may devirtualise calls.
// ===== When to use class vs struct =====
// CLASS:
// - Identity matters (database entity, persistent object)
// - Shared mutable state
// - Interop with Objective-C / Cocoa frameworks
// - Deep inheritance hierarchies (rare; prefer composition)
// STRUCT:
// - Value types (Money, Point, Vector)
// - Small / immutable
// - Most modern Swift code
// ===== Patterns to internalise =====
// - Default to struct; reach for class only when you need identity / inheritance
// - weak / unowned for back-references to avoid cycles
// - [weak self] in closures
// - final by default; open only when designed for inheritance
// ===== Pitfalls =====
// - Inheriting deeply when composition fits better
// - Forgetting [weak self] in long-lived closures -> retain cycles
// - Sharing a class instance and mutating from multiple owners -> race / surprise
// - Subclassing for reuse instead of behaviour relationship
Why it matters
Classes are Swift reference types: identity, inheritance, deinit. Reach for them when you need shared mutable state or framework interop; default to struct otherwise. The discipline (weak back-refs, final by default, composition over inheritance) keeps hierarchies shallow and predictable.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
class Counter {
var n = 0
func bump() { n += 1 }
}
let c = Counter()
c.bump()
Try it Yourself »
Discussion
Loading…