Structs, classes and protocols
Value versus reference semantics, when inheritance is the right tool, and how protocols drive reuse in Swift.
Structs: value semantics
struct Point: Equatable {
var x: Double
var y: Double
// memberwise init is generated, so Point(x: 1, y: 2) already works
}
struct Counter {
private(set) var value = 0
mutating func increment() { value += 1 } // mutating: changes a value type
}
var a = Point(x: 1, y: 2)
var b = a // a copy
b.x = 99
print(a.x) // 1 - unaffected
var counter = Counter()
counter.increment()| Aspect | struct | class |
|---|---|---|
| Semantics | Copy on assignment | Shared reference |
| Inheritance | No | Yes, single inheritance |
| Stored property mutation | Needs mutating | Any method |
| Identity comparison | No (== only) | === compares identity |
| Memory management | None to think about | ARC, reference cycles possible |
| Default choice | Yes | Only when you need sharing or inheritance |
Classes and inheritance
class Account {
let owner: String
private(set) var balance: Int
init(owner: String, opening: Int = 0) {
self.owner = owner
self.balance = opening
}
func deposit(_ amount: Int) {
precondition(amount > 0, "amount must be positive")
balance += amount
}
deinit { print("closing " + owner) }
}
final class Savings: Account {
override func deposit(_ amount: Int) {
super.deposit(amount)
}
// a designated init must call a designated init of the superclass
init(owner: String) { super.init(owner: owner, opening: 0) }
}The compiler inserts strong references by default. A closure stored on an object that captures that object creates a retain cycle, and the memory is never released — break it by capturing [weak self] or [unowned self].
Protocols and default implementations
A protocol declares a contract; a class, struct or enum adopts it. Requirements can be given default implementations in a protocol extension, which turns the protocol into a reusable behaviour that works for any conforming type.
protocol Identifiable {
var id: String { get }
}
protocol Persistable {
func save() throws
func describe() -> String
}
extension Persistable {
func describe() -> String { return "record " + id } // reusable default
}
struct Note: Identifiable, Persistable {
let id: String
var title: String
func save() throws {
guard !title.isEmpty else { throw NoteError.emptyTitle }
}
}
enum NoteError: Error { case emptyTitle }
// composition: any type satisfying both contracts
func store(_ item: Note, using writer: some Persistable) throws {
try writer.save()
}💡
Prefer protocol composition and generics (
some, <T: P>) over Any or class hierarchies. Protocols let a struct, an enum and a class be interchangeable wherever the contract is all you need.FAQ
Struct or class?
Default to a struct. Choose a class when identity matters (two objects must be literally the same instance), when you need inheritance, or when the framework requires it such as
NSManagedObject or a UIViewController.Why is my object never deallocated?
Almost always a retain cycle: a closure property capturing the object itself, a delegate declared
strong, or two objects referencing each other. Declare delegates weak and capture [weak self] in escaping closures.Related
Closures and error handling Syntax and optionals
Last refreshed 2026-09-18.