What is a capture list in a Swift closure?
TL;DR
A capture list is the [ ... ] at the start of a closure. It says how the closure should capture things.
Two main uses: break retain cycles with [weak self] or [unowned self], and copy a value at creation time with [value].
Without a capture list, a closure captures variables by reference and objects strongly.
Default capture: by reference
var count = 0
let printCount = { print(count) }
count = 5
printCount() // 5, not 0The closure sees the variable itself, not a snapshot of it.
Capture a copy with a capture list
var count = 0
let printCount = { [count] in print(count) }
count = 5
printCount() // 0[count] copies the value when the closure is created. Later changes do not affect it. The captured copy is a constant.
Break retain cycles with [weak self]
final class TimerViewModel {
var ticks = 0
var onTick: (() -> Void)?
func start() {
onTick = { [weak self] in
guard let self else { return }
self.ticks += 1
}
}
deinit { print("TimerViewModel freed") }
}Without [weak self]: the object holds onTick, and onTick holds the object. Neither is freed.
With [weak self]: the closure does not keep the object alive. self becomes an optional inside the closure.
[unowned self]
lazy var title: () -> String = { [unowned self] in
"Ticks: \(self.ticks)"
}unowned does not keep the object alive either. But it is not optional. If the object is gone when the closure runs, the app crashes.
Use unowned only when the closure can never outlive self.
weak vs unowned in closures
[weak self] | [unowned self] | |
|---|---|---|
| Type inside closure | Self? | Self |
| If object is gone | self is nil | Crash |
| Safe default? | Yes | Only if lifetime is guaranteed |
Capture other things too
You can capture several values and give them new names.
let service = APIService()
loadButton.onTap = { [weak self, service, limit = pageSize * 2] in
self?.load(using: service, limit: limit)
}serviceis captured strongly (fine, it does not hold the closure).limitis computed once when the closure is created.
You can also make other objects weak, like a delegate or a view:
{ [weak delegate, weak tableView] in
tableView?.reloadData()
}When you do NOT need [weak self]
- Non-escaping closures, like
mapandfilter. - Closures that run once and are not stored by
self, likeUIView.animateor a shortDispatchQueue.main.async. They holdselfa little longer, then release it. Task { }that finishes quickly. It keepsselfalive until it ends, but does not create a permanent cycle.
Be careful with a Task that never ends, such as one that loops over an AsyncStream. It keeps self alive forever. Store the task and cancel it.
final class FeedViewModel {
private var task: Task<Void, Never>?
func start(stream: AsyncStream<Int>) {
task = Task { [weak self] in
for await value in stream {
self?.handle(value)
}
}
}
func handle(_ value: Int) { }
deinit { task?.cancel() }
}Note: writing guard let self at the top of a long-running loop would hold self strongly for the whole loop. Use self?. inside the loop instead.
Common follow-up questions
- Does
[weak self]work with structs? No.weakonly works with class (reference) types. Structs are copied, so there is no cycle. - What does
guard let self else { return }do? It makes a strong reference for the rest of the closure body, soselfcannot disappear in the middle. - Is a captured value in
[x]mutable? No. It is a constant copy inside the closure. - Do capture lists matter for value types? Yes, for snapshot behavior.
[count]copies the current value instead of reading the variable later. - Is
[unowned self]faster? Slightly, because there is no optional check. The safety risk is usually not worth it.