Is it always necessary to use weak or unowned in the Capture List?
sobes.tech AI
Answer from AI
No, not always.
We use weak or unowned in Capture List to prevent retain cycles.
A retain cycle occurs when two or more objects strongly reference each other, preventing memory from being released.
Use weak or unowned when a closure strongly references self (or another instance variable of a class/actor), and in turn, self (or another object) has a strong reference to that closure.
Examples of situations where this is necessary:
- Asynchronous operations (network requests, data processing)
- Observer pattern
- Animation blocks
- Dispatch queues
If a closure does not capture self (or another class/actor variable that has a strong reference to the closure), or captures structural types/enumerations, then using weak or unowned is not required.
Difference between weak and unowned:
| Type | Description | When to use |
|---|---|---|
weak |
Weak (optional) reference. Can become nil. |
When the lifecycle of the closure is shorter or equal to the lifecycle of the captured object, and the object may become nil before the closure finishes. |
unowned |
Non-optional reference. Cannot become nil. |
When the lifecycle of the closure is shorter or equal to the lifecycle of the captured object, and it is guaranteed that the object will exist until the closure completes. If the object is deallocated before the closure executes, a crash occurs (unowned reference to deallocated instance). |
Example of using weak in Capture List:
class MyClass {
var name: String = "Test"
func performAsyncOperation() {
// Closure captures self.
// MyClass may be deallocated before the async operation completes.
DispatchQueue.global().async { [weak self] in
guard let self = self else {
// self was deallocated, exit
return
}
print("Operation completed for \(self.name)")
}
}
deinit {
print("MyClass deallocated")
}
}
Example of using unowned in Capture List:
class ViewController {
var dismissButton: UIButton!
func setupButton() {
dismissButton.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
// Closure captures self.
// Assuming that `self` (ViewController) lives longer or equal to the button action.
// The target-action is not a closure, but illustrates the concept of unowned with strong reference cycle.
// If this *were* a closure, for example, in a UIControl.Event handler,
// and ViewController is guaranteed to exist during button press:
// dismissButton.addAction(UIAction { [unowned self] _ in
// self.dismiss(animated: true, completion: nil)
// }, for: .touchUpInside)
}
@objc func dismiss() {
// Dismiss view controller
}
}
If a closure does not create a retain cycle, using weak or unowned is unnecessary and may even lead to unnecessary checks (weak) or potential crashes (unowned).
For example, a closure that does not capture self or another object that has a strong reference to the closure:
func fetchData(completion: @escaping (Data) -> Void) {
URLSession.shared.dataTask(with: URL(string: "...")!) { data, response, error in
guard let data = data else { return }
// No capture of self or other objects that could cause retain cycle.
completion(data)
}.resume()
}
In this case, [weak self] or [unowned self] in the Capture List is not required.