Is using [weak self] always required when working with closures?
Basics article available: Memory ManagementWhen an object or value is referenced within an escaping closure, it gets captured in order to be available when that closure is executed. By default, when an object gets captured, it will be retained using a strong reference — which in the case of self
could lead to retain cycles in certain situations.
For example, the following code captures self
strongly within a closure that’s also itself ultimately retained by that same object, leading to a retain cycle:
class ProductViewController: UIViewController {
private lazy var buyButton = Button()
private let purchaseController: PurchaseController
...
override func viewDidLoad() {
super.viewDidLoad()
// Since our buyButton retains its closure, and our
// view controller in turn retains that button, we'll
// end up with a retain cycle by capturing self here:
buyButton.handler = {
self.showShoppingCart()
self.purchaseController.startPurchasingProcess()
}
}
private func showShoppingCart() {
...
}
}
To fix the above problem we can, for instance, use a [weak self]
capture list — which will break our retain cycle by now capturing self
using a weak reference:
buyButton.handler = { [weak self] in
guard let self = self else { return }
self.showShoppingCart()
self.purchaseController.startPurchasingProcess()
}
However, that doesn’t mean that capturing self
always results in a retain cycle — it’s really only in the above type of situation that such a problem could occur. For example, the following code does not cause a retain cycle:
class HomeViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
if shouldShowPromotion {
// Capturing self within this closure does not cause
// a retain cycle, since our view controller does not
// retain this dispatch queue in any way:
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.showPromotionView()
}
}
}
private func showPromotionView() {
...
}
}
One thing that’s important to consider though, is that when capturing self
like we do above, we are retaining it for as long as the closure itself will remain in memory (in this case, for two seconds). So while we might not technically cause a retain cycle in those situations, we could end up prolonging the lifetime of certain objects, which might lead to unexpected behavior.
So, to sum up:
- Using
[weak self]
is only required within situations in which capturingself
strongly would end up causing a retain cycle, for example whenself
is being captured within a closure that’s also ultimately retained by that same object. - Using
[weak self]
can also be a good idea when working with closures that will be stored for a longer period of time, as capturing an object strongly within such a closure will cause it to remain in memory for that same amount of time. - In all other situations, using
[weak self]
is optional, but there’s typically no harm in adding it — unless we want to captureself
strongly for some reason.
There are also sometimes ways that we can avoid using capture lists entirely, some of which were covered in “Capturing objects in Swift closures”.