What are the implications of using strong, weak, and unowned references in Swift closures?

Instruction: Explain how strong, weak, and unowned references affect the capture of self in closures and provide examples.

Context: This question aims to evaluate the candidate’s understanding of memory management and capture lists in Swift closures, focusing on avoiding retain cycles.

Updated

Official answer

A closure normally retains captured class instances strongly. A cycle arises when an owner retains a closure that in turn retains that owner; a strong capture alone is not necessarily a leak. A weak capture does not keep the instance alive and becomes nil after deallocation. A checked unowned capture also does not retain it, but accessing it after deallocation traps.

This complete synchronous example demonstrates the different lifetimes without intentionally crashing:

final class Counter {
    var value = 0
    func increment() { value += 1 }
    func strongly() -> () -> Void { { self.increment() } }
    func weakly() -> () -> Void { { [weak self] in self?.increment() } }
    func unowned() -> () -> Void { { [unowned self] in self.increment() } }
}
var owner: Counter? = Counter()
weak var witness = owner
var strongCallback: (() -> Void)? = owner!.strongly()
owner = nil
strongCallback?()
assert(witness?.value == 1)
strongCallback = nil
assert(witness == nil)

var temporary: Counter? = Counter()
let weakCallback = temporary!.weakly()
temporary = nil
weakCallback() // Missing target is deliberately a no-op.

let live = Counter()
let unownedCallback = live.unowned()
withExtendedLifetime(live) { unownedCallback() }
assert(live.value == 1)

The force unwraps above access locally established nonnil fixture values. Avoid this pattern for untrusted input. For an escaping callback whose owner may disappear, a weak capture and an explicit missing-owner policy are often appropriate. If you unwrap a weak reference into a local strong reference, that local keeps the object alive for its use, potentially across suspension in an async function.

Use unowned only with a proven lifetime contract; mere containment or an expectation that a screen remains visible is insufficient. A weak capture breaks that particular ownership edge, not every cycle involving other captures. Inspect the entire graph, cancellation and callback removal, and test that objects deallocate when expected.

Swift reference ownership

Related Questions