03 · What does this print? — closures and values
Value vs reference semantics, capture lists, loop captures and defer — five lines of output to predict.
Shape: what does this print · Reported: closure capture and escaping questions at Revolut and Delivery Hero · Verified: run with Swift 6.4
"Line by line — what prints?"
struct Counter { var value = 0 }
var a = Counter()
var b = a
b.value += 1
print(a.value, b.value) // ①
var x = 1
let byReference = { print("ref", x) }
let byValue = { [x] in print("value", x) }
x = 2
byReference() // ②
byValue() // ③
final class Box { var value = 0 }
let box = Box()
let snapshot = { [box] in print("box", box.value) }
box.value = 5
snapshot() // ④
var handlers: [() -> Void] = []
for i in 0..<3 { handlers.append { print("loop", i) } }
handlers.forEach { $0() } // ⑤
func f() -> Int {
var n = 1
defer { n += 10; print("defer", n) }
n += 1
return n
}
print("returned", f()) // ⑥A hint, if you're stuck
- Is
Countera value type or a reference type? What doesb = acopy? - A closure without a capture list sees the variable. With
[x]it takes a copy — but a copy of what, for a class? - When exactly does
deferrun, compared withreturn?
Answers (verified output)
0 1
ref 2
value 1
box 5
loop 0
loop 1
loop 2
defer 12
returned 2- ① Structs are values:
b = acopies. Changingbleavesaalone. - ② A closure captures the variable, not its value at the time. It sees
x = 2. - ③ A capture list copies the value when the closure is created:
1. - ④ The trap.
[box]copies the reference. It's still the same object, so it sees5. Capture lists snapshot values, not objects. - ⑤ Each loop iteration has its own
i, so0 1 2. (In some other languages this prints3 3 3; saying so shows you know why Swift doesn't.) - ⑥
return nevaluates2first, thendeferruns and changesnto12— too late to affect the returned value. Printsdefer 12beforereturned 2, becausef()must finish before the outerprintcan.
The idea behind it
Swift has two kinds of types. A value type (a struct or an enum) behaves like a photocopy: assign it and you get an independent copy. A reference type (a class) behaves like a house address: copy it and you have two slips of paper pointing at the same house. Paint the house through one slip and the other slip sees the new colour.
Closures capture the outside variables they use. By default they capture the variable itself, so they see any later change to it. A capture list like [x] takes a copy at the moment the closure is created. But copying a class reference copies the address, not the house — so the closure still sees changes made to the object. That is exactly why [weak self] works: it changes how the closure holds the address (without keeping the house standing), not what's inside.
defer runs when the function is on its way out — after the return value has already been worked out. Changing a variable in defer can't change what was returned.
How to make it unsurprising
let box = Box()
let snapshot = { [value = box.value] in print("box", value) }
box.value = 5
snapshot() // box 0- To freeze an object's state, capture the value, not the object.
[value = box.value]copies theIntwhen the closure is made;[box]only copies the address. - Write the capture list on purpose whenever a closure outlives the line it's on, so the reader sees what's a snapshot and what's live.
- Use
deferfor clean-up only — closing, unlocking, resetting. Never to change a value you return.
What I'm really scoring
④ separates people who memorised "capture lists copy" from people who understand that a class reference is itself the value being copied. That same understanding is what makes [weak self] make sense — you're choosing how the closure holds the reference, not copying the object.
What I'd ask next
- "What makes a closure escaping, and why does Swift make you say so?" — It can be called after the function returns (stored, or passed to async work). Escaping closures can create retain cycles and need explicit
self; non-escaping ones can't outlive the call, so the compiler can optimise them and lets you omitself. Since Swift 5.3 (SE-0269) you can also omit it in an escaping closure that captures[self]explicitly, or whenselfis a struct or enum. - "When is
[weak self]unnecessary?" — When the closure isn't stored by anythingselfowns:UIView.animate,map, a one-shot request you're happy to let finish. - "
weakvsunowned?" —unownedcrashes if the object is gone; use it only when the closure provably can't outlive the object. - "Change
Counterto a class — what does ① print?" —1 1: both names point at one object.