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.
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.