Skip to content
SwiftMind

Closure Capture

Capture lists, and deciding what a stored closure should keep alive.

AdvancedNot started8 min
On this page

When a closure mentions a variable from its surrounding scope, it captures it — and if that variable is a class reference, the capture is strong by default. The object stays alive for as long as the closure does.

A capture list, the [...] before the parameters, changes that. [weak self] captures self weakly, making it optional inside. [unowned self] captures it without a strong reference and without zeroing. You can also capture a snapshot: [value] copies the current value rather than referring to the variable.

The decision is always the same question: should this closure keep this object alive until it runs?

Capturing a value instead of self sidesteps the lifetime question entirely.
final class Downloader {    var completion: (() -> Void)?    var progress = 0.0
    func brokenSetup() {        completion = { self.finish() }        // cycle: self → closure → self    }
    func fixedSetup() {        completion = { [weak self] in            guard let self else { return }    // object gone: do nothing, deliberately            self.finish()        }    }
    func snapshot() {        let at = progress        completion = { print("was \(at)") }   // captures a copy, not self    }
    func finish() {}}
Show all 22 lines
A closure cycle and how a capture list breaks it
Downloaderowns the closure
closurecaptures self
closure [weak self]captures weakly
Freedcycle broken
Leakedcycle intact

A Downloader holds a closure in a stored property, and the closure strongly captures the Downloader, forming a loop. A weak capture replaces that back-edge so the loop is broken.

The object stores the closure, and the closure captures the object. That's the loop.

Step 1 of 3
  1. The object stores the closure, and the closure captures the object. That's the loop.
  2. Neither count can reach zero, so both leak.
  3. A weak capture removes the back-edge. The object can now be freed, and the closure's `self` simply becomes nil.

Under the hood

A capture list is evaluated *when the closure is created*, not when it runs. [weak self] takes a weak reference at creation; [value] copies whatever value holds at that instant, and later changes to the variable don't affect the captured copy.

This is why [value] is a genuine third option rather than a curiosity: it converts a reference-to-storage capture into a snapshot, which is often what you actually wanted and removes the lifetime question completely.

guard let self else { return } works because Swift 5.8 allows shadowing self in a guard. It promotes the weak reference to a strong one for the body's duration, so self can't be deallocated halfway through — without it, each separate self?. could see a different answer as another thread releases the last reference between them.

Quick check

What does this print?

  • 10
  • 99
  • nil
  • It doesn't compile
Reveal answer

10

A capture list is evaluated when the closure is *created*, so [x] copies the value 10 at that moment. Reassigning x afterwards changes the variable, not the captured copy. Without the capture list the closure would refer to the variable's storage and print 99 — which is the distinction the capture list exists to give you.

Interview question

How do you reason about closure captures when reviewing code?

Think first, then reveal

I read every stored or escaping closure as a lifetime statement and ask two things: what does this keep alive, and what happens if the thing it references is gone when it runs?

The first question finds cycles. If the closure is reachable from the object it captures — stored on it, held in a cancellable set on it, registered with something the object owns — that's a loop, and the capture needs weakening.

The second question is the one reviewers skip, and it finds the subtler bug. Once you weaken the capture, you've decided that if the object dies the work doesn't happen. For a UI update that's correct and desirable. For "flush the cache", "release the lock", "resume the continuation" or "call the completion handler" it's a silent failure with no crash and nothing in the logs, which is much harder to diagnose than the leak it replaced. A continuation that never resumes is the worst version — the awaiting task hangs forever.

So the review outcome isn't always "add [weak self]". Sometimes it's "this closure should capture strongly and this object should live until the work finishes, and the reason belongs in a comment". Sometimes the best answer is to capture the two values it actually needs instead of the whole object, which makes both questions disappear.

I also look for whether the closure needs self at all — a surprising number capture it for one property that could have been snapshotted.

What an interviewer listens for

  • Reads captures as lifetime decisions rather than pattern-matching on syntax
  • Asks both what's kept alive and what happens when it's gone
  • Names silent no-ops — especially unresumed continuations — as the cost of weak capture
  • Suggests value capture as a way to remove the question entirely
  • Concludes that weak self is not the universal answer

Key takeaway

Capture lists are evaluated at closure creation and decide what the closure keeps alive. Weak when self owns the closure; strong when the work must complete; a value snapshot when you never needed the object at all.

Read these first

Related

0%
Quiz this concept