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()
Aspectstructclass
SemanticsCopy on assignmentShared reference
InheritanceNoYes, single inheritance
Stored property mutationNeeds mutatingAny method
Identity comparisonNo (== only)=== compares identity
Memory managementNone to think aboutARC, reference cycles possible
Default choiceYesOnly 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.

Closures and error handling Syntax and optionals

Last refreshed 2026-09-18.