Home

Memory and ownership

ARC, cycles, exclusivity, and the unsafe escape hatches

Swift manages class instances with automatic reference counting. Not a tracing garbage collector: no background thread, no pauses, deallocation happens the instant the last reference goes away. The trade is that the counting is real work, and that ARC cannot break cycles.

Where the retains actually are

ARC inserts retain and release calls around every place a reference is stored or passed. Most are optimised away, but the ones that survive are the reason a tight loop over class instances can be several times slower than the same loop over structs — reference counting is atomic, so every retain is a synchronised operation even in single-threaded code.

This is a concrete argument for value types in hot paths, and an argument for final:

final class Node { var value = 0 }

final removes the possibility of a subclass, which lets the compiler devirtualise method calls and sometimes stack-promote the instance entirely.

Cycles

Two objects holding strong references to each other keep each other alive forever:

final class Parent { var child: Child? }
final class Child { var parent: Parent? }   // ← leak

The fix is to make one direction non-owning:

final class Child { weak var parent: Parent? }
  • weak — becomes nil when the target dies, so it must be Optional and var. Costs a side table entry and a check on every access.
  • unowned — does not become nil; accessing it after the target dies traps. Cheaper, and correct when the reference genuinely cannot outlive its target.

Prefer weak unless you can state why the lifetime is guaranteed. “It crashed in production” is a worse outcome than “it was nil and we handled it”.

Closures capture strongly

The most common cycle in real applications is a closure retained by the object it captures:

final class ImageLoader {
    var onFinish: (() -> Void)?

    func load() {
        fetch { data in
            self.store(data)   // self is captured strongly; self holds onFinish
        }
    }
}

Capture lists break it:

fetch { [weak self] data in
    guard let self else { return }
    self.store(data)
}

guard let self else { return } is the idiom — it takes a strong reference for the duration of the closure body, so self cannot be deallocated halfway through.

Tip

Not every closure needs [weak self]. A closure that runs and is released immediately — array.map { self.transform($0) } — cannot create a cycle, because nothing stores it. Adding [weak self] there is noise that trains readers to ignore the annotation where it matters.

Exclusivity

Swift enforces that a variable is not accessed for writing while it is being accessed at all. Some of this is caught at compile time, the rest at runtime:

var total = 0

func accumulate(into value: inout Int, from other: Int) { value += other }

accumulate(into: &total, from: total)   // fine: `from` is read before the call
// swap(&total, &total)                 // error: overlapping access

The rule exists so the optimiser can assume an inout parameter is not aliased, which is what makes in-place mutation of value types fast. Runtime exclusivity checking is on in debug and, since Swift 5, in release too — if you have ever seen “Simultaneous accesses to 0x…”, this is it.

Unsafe, when you mean it

Sometimes you need the bytes. The Unsafe*Pointer family gives you them, with a naming convention that tells you exactly how much rope you are holding:

var samples = [Float](repeating: 0, count: 1024)

samples.withUnsafeMutableBufferPointer { buffer in
    for index in buffer.indices {
        buffer[index] *= 0.5
    }
}

The withUnsafe… closure form is the safe way to be unsafe: the pointer is valid for the duration of the closure and not one instruction longer. The cardinal rule is never to let it escape —

var escaped: UnsafeMutableBufferPointer<Float>?
samples.withUnsafeMutableBufferPointer { escaped = $0 }   // dangling the moment this returns

— because the array is free to reallocate its buffer the moment the closure ends, and the compiler will not stop you.