BBMM Technologies
← All articles
7 min readswift, memory-management, arc, ios

Memory-Safe Patterns in Swift

By Mykhailo Boichuk · Co-founder & Vice-President

In short

Swift uses automatic reference counting rather than a garbage collector, which makes memory behavior deterministic but puts the burden of avoiding retain cycles on the developer. The reliable patterns are to understand strong, weak, and unowned references, to break cycles in closures with capture lists, and to favor value types where reference semantics are not needed.

ARC is deterministic, not automatic in the GC sense

Swift manages memory for reference types with automatic reference counting (ARC). Each object keeps a count of the strong references to it, and when that count reaches zero the object is deallocated immediately. This is different from a tracing garbage collector: deallocation is deterministic and happens at a known point, which is good for predictability but means the developer must avoid the one failure mode ARC cannot resolve on its own.

That failure mode is the retain cycle. When two objects hold strong references to each other, neither count ever reaches zero, so neither is deallocated, even when nothing else refers to them. The memory is leaked. Most Swift memory problems are some version of this.

Strong, weak, and unowned

Swift gives three reference kinds, and choosing correctly among them is the core of memory safety. A strong reference keeps the target alive. A weak reference does not, and becomes nil when the target is deallocated, so it must be optional. An unowned reference also does not keep the target alive but is assumed never to be nil while in use.

  • Use strong references for ownership, where the holder should keep the target alive.
  • Use weak references for back-references and delegates, where the target may outlive or be outlived by the holder.
  • Use unowned only when the target is guaranteed to outlive the reference, since accessing it after deallocation is a crash.

Closures are where cycles hide

Closures capture the values they reference, and by default they capture them strongly. When an object stores a closure that refers back to that same object, a cycle forms quietly. This is one of the most common leaks in Swift code, precisely because the capture is implicit.

A capture list breaks the cycle. Writing the capture as weak or unowned in the closure’s definition tells the closure not to keep the captured object alive, which is the standard remedy for a closure that refers to its owner.

Prefer value types where you can

Many memory problems disappear by not using reference types at all. Swift’s structs and enums are value types: they are copied rather than shared, so they cannot participate in retain cycles and have no lifetime to manage. Modeling data as value types where shared identity is not required removes an entire category of bugs.

The practical posture is to reach for value types by default, use reference types when shared mutable identity is genuinely needed, and then be deliberate about reference kinds and closure captures. Verifying with the memory tools in Instruments catches the cycles that slip through. Memory safety in Swift is less about cleverness than about consistently applying a small set of rules.

Key takeaways

  • Swift uses ARC, so deallocation is deterministic but retain cycles must be avoided by hand.
  • A retain cycle occurs when two objects strongly reference each other and neither is freed.
  • Choose strong, weak, or unowned references based on ownership and expected lifetimes.
  • Break closure-induced cycles with a weak or unowned capture list.
  • Prefer value types, which cannot form cycles, where shared identity is not required.

Frequently asked questions

What is a retain cycle in Swift?
It occurs when two reference-counted objects hold strong references to each other, so neither reference count reaches zero and neither object is ever deallocated, leaking the memory.
When should I use unowned instead of weak?
Use unowned only when the referenced object is guaranteed to outlive the reference, since unlike weak it does not become nil and accessing it after deallocation causes a crash.
How do I avoid memory leaks in closures?
Use a capture list that captures the relevant object weakly or unowned, so the closure does not keep its owner alive and a cycle cannot form.

References

About the author

Mykhailo Boichuk

Co-founder & Vice-President

Mykhailo is an engineer who builds native applications and the systems behind them. He concentrates on macOS and iOS performance, local-first data architecture, and the synchronization problems that come with offline-capable software.